--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry kind="src" path="src"/>\r
+ <classpathentry kind="src" path="resources"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>jatty</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8\r
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve\r
+org.eclipse.jdt.core.compiler.compliance=1.8\r
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate\r
+org.eclipse.jdt.core.compiler.debug.localVariable=generate\r
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.8\r
--- /dev/null
+/de/
+/jatty.de.properties
+/jatty.en.properties
+/talk_schema.sql
+/user_schema.sql
--- /dev/null
+0=Gebrauch:
+1=Startet einen Chat-Server, einen GUI-Chat-Client oder einen Kommandozeilen-Chat-Client.
+2=Adresse des Chat-Servers, z.B. localhost oder
+3=Voreinstellung:
+4=Port des Servers.
+5=Voreinstellung:
+6=Beispiele:
+7=Modus fehlt:
+8=Server angehalten
+9=Sprache der Oberfläche. Voreinstellung: Systemeinstellung
+10=Willkommen bei
+11=Willkommen %s
+12=Es fehlen Benutzername und Passwort: z.B.
\ No newline at end of file
--- /dev/null
+0=Usage:
+1=Starts a chat server, a GUI chat client or a commandline chat client.
+2=address of the chat server, e.g. localhost or
+3=Default:
+4=port of the server.
+5=Default:
+6=Examples:
+7=missing mode:
+8=server stopped
+9=language of the GUI. Default: system setting
+10=Welcome to
+11=Welcome %s
+12=missing username and password: e.g.
\ No newline at end of file
--- /dev/null
+CREATE TABLE talk (\r
+ talkId int identity,\r
+ time timestamp,\r
+ user varchar(64),\r
+ message varchar(254)\r
+);\r
+insert into talk (time, user, message) values('2016-03-16 08:10:32', 'hm', 'Hi all');
\ No newline at end of file
--- /dev/null
+CREATE TABLE user (\r
+ user varchar(64),\r
+ passwdHash varchar(256),\r
+ lastLogin TIMESTAMP,\r
+ UNIQUE (user)\r
+);\r
+insert into user (user, passwdHash, lastLogin) values('hm', '', '2016-03-16 08:24:33');\r
--- /dev/null
+/**\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.Socket;\r
+import java.net.UnknownHostException;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+\r
+/**\r
+ * A TCP chat client.\r
+ *\r
+ * @author hm\r
+ *\r
+ */\r
+class Client {\r
+ String address;\r
+ int port = 0;\r
+ Socket clientSocket = null;\r
+ BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));\r
+ DataOutputStream outToServer = null;\r
+ BufferedReader inFromServer = null;\r
+ Scrambler scrambler;\r
+\r
+ /**\r
+ * Constructor.\r
+ *\r
+ * @param address\r
+ * the address of the server, e.g. "localhost" or "192.168.2.1"\r
+ * @param port\r
+ * the port of the server\r
+ */\r
+ public Client(final String address, final int port,\r
+ final Scrambler scrambler) {\r
+ this.port = port;\r
+ this.address = address;\r
+ this.scrambler = scrambler;\r
+ try {\r
+ this.clientSocket = new Socket(address, port);\r
+ this.outToServer = new DataOutputStream(this.clientSocket.getOutputStream());\r
+ this.inFromServer = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));\r
+ } catch (final UnknownHostException e) {\r
+ e.printStackTrace();\r
+ this.clientSocket = null;\r
+ } catch (final IOException e) {\r
+ e.printStackTrace();\r
+ this.clientSocket = null;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Writes a message to the user interface.\r
+ *\r
+ * @param msg\r
+ * the message to write\r
+ */\r
+ public void log(final String msg) {\r
+ System.out.println(msg);\r
+ }\r
+\r
+ /**\r
+ * Sends a message and receives the answer.\r
+ *\r
+ * @param message\r
+ * the message to send\r
+ * @return the answer of the server a a string list\r
+ */\r
+ public List<String> sendAndReceive(String message) {\r
+ final List<String> answer = new LinkedList<String>();\r
+ try {\r
+ final boolean isUpdate = message.startsWith("/UPDATE");\r
+ if (!isUpdate)\r
+ log("> " + message);\r
+ message = this.scrambler.encode(message + "\n");\r
+ this.outToServer.writeBytes(message);\r
+ while (this.inFromServer.ready()) {\r
+ String line = this.inFromServer.readLine();\r
+ line = this.scrambler.decode(line);\r
+ if (isUpdate && answer.size() == 0 && line.length() > 0)\r
+ log("> " + message);\r
+ log("< " + line);\r
+ answer.add(line);\r
+ }\r
+ } catch (final IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ return answer;\r
+ }\r
+\r
+ /**\r
+ * Runs the client untils the server sends "/EXIT".\r
+ *\r
+ * Only used by the command line client.\r
+ */\r
+ public void run() {\r
+ String message;\r
+ List<String> answer = null;\r
+ try {\r
+ while (answer == null || answer.size() == 0\r
+ || answer.get(answer.size() - 1).startsWith("/EXIT")) {\r
+ message = this.inFromUser.readLine();\r
+ answer = sendAndReceive(message);\r
+ }\r
+ } catch (final IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ try {\r
+ this.clientSocket.close();\r
+ } catch (final IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package de.hamatoma.jatty;
+
+import java.awt.BorderLayout;
+import java.awt.EventQueue;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.List;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextArea;
+import javax.swing.ListSelectionModel;
+import javax.swing.Timer;
+
+/**
+ * A chat client with a Swing GUI.
+ *
+ * @author hm
+ *
+ */
+public class ClientGui extends Client implements KeyListener, ActionListener {
+
+ private JFrame frame;
+ private JTextArea textAreaCommand;
+ private JTextArea textAreaStatusLine;
+ private JTextArea textAreaHeader;
+ private final DefaultListModel<String> talkListModel = new DefaultListModel<String>();
+
+ /**
+ * A main program for testing.
+ *
+ * @param args
+ * program arguments
+ */
+ public static void main(final String[] args) {
+ final String addr = args.length < 1 ? "localhost" : args[0];
+ final int port = args.length < 2 ? 58123 : Integer.parseInt(args[1]);
+ final Scrambler scrambler = new Scrambler("a", "b", "c");
+ final ClientGui client = new ClientGui(addr, port, scrambler);
+ client.run();
+ }
+ /**
+ * Launch the application.
+ */
+ @Override
+ public void run() {
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ClientGui.this.frame.setVisible(true);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ Timer timer = null;
+ private JScrollPane scrollPaneTalk;
+ private JList<String> listTalk;
+ private JScrollPane scrollPaneUser;
+ private JList<String> listUser;
+
+ /**
+ * Create the application.
+ *
+ * @param address
+ * the server address, e.g. "localhost" or "123.2.4.8"
+ * @param port
+ * the server port
+ */
+ public ClientGui(final String address, final int port,
+ final Scrambler scrambler) {
+ super(address, port, scrambler);
+ initialize();
+ this.timer = new Timer(1000, this);
+ this.timer.start();
+ }
+
+ /**
+ * Initialize the contents of the frame.
+ */
+ private void initialize() {
+ this.frame = new JFrame();
+ this.frame
+ .setTitle("jatty client [" + this.address + ":"
+ + this.port + "]");
+ this.frame.setBounds(100, 100, 450, 300);
+ this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.frame.getContentPane().setLayout(new BorderLayout(0, 0));
+
+ final JPanel panel = new JPanel();
+ this.frame.getContentPane().add(panel);
+ panel.setLayout(new BorderLayout(0, 0));
+
+ final JSplitPane splitPane = new JSplitPane();
+ splitPane.setDividerLocation(0.80);
+ splitPane.setResizeWeight(0.9);
+ splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
+ panel.add(splitPane, BorderLayout.CENTER);
+
+ this.textAreaCommand = new JTextArea();
+ this.textAreaCommand.setTabSize(3);
+ this.textAreaCommand.addKeyListener(this);
+ this.textAreaCommand.setText("/LOG ");
+ splitPane.setRightComponent(this.textAreaCommand);
+
+ final JSplitPane splitPaneOutput = new JSplitPane();
+ splitPaneOutput.setDividerLocation(0.80);
+ splitPaneOutput.setResizeWeight(0.8);
+ splitPane.setLeftComponent(splitPaneOutput);
+
+ this.scrollPaneTalk = new JScrollPane();
+ splitPaneOutput.setLeftComponent(this.scrollPaneTalk);
+
+ this.listTalk = new JList<String>(this.talkListModel);
+ this.listTalk.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ this.scrollPaneTalk.setViewportView(this.listTalk);
+
+ this.scrollPaneUser = new JScrollPane();
+ splitPaneOutput.setRightComponent(this.scrollPaneUser);
+
+ this.listUser = new JList<String>();
+ this.listUser.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ this.scrollPaneUser.setViewportView(this.listUser);
+
+ this.textAreaStatusLine = new JTextArea();
+ this.textAreaStatusLine.setEditable(false);
+ this.textAreaStatusLine.setText("Started");
+ this.frame.getContentPane().add(this.textAreaStatusLine, BorderLayout.SOUTH);
+
+ this.textAreaHeader = new JTextArea();
+ this.textAreaHeader.setEditable(false);
+ this.textAreaHeader.setText(
+ I18N.tr(I18N.WELCOME, "Welcome to") + " jatty " + Jatty.VERSION);
+ this.frame.getContentPane().add(this.textAreaHeader, BorderLayout.NORTH);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
+ */
+ @Override
+ public void keyTyped(final KeyEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
+ */
+ @Override
+ public void keyPressed(final KeyEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
+ */
+ @Override
+ public void keyReleased(final KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ if (e.getSource() == this.textAreaCommand){
+ final String text = this.textAreaCommand.getText();
+ final List<String> answer = this.sendAndReceive(text);
+ handleAnswer(answer);
+ this.textAreaCommand.setText("");
+ }
+ }
+ }
+
+ /**
+ * Evaluates the server answer.
+ *
+ * @param answer
+ * the answer string list
+ */
+ void handleAnswer(final List<String> answer) {
+ for (final String line : answer) {
+ if (line.startsWith("/USER")) {
+ final String[] users = line.substring(6).split(" ");
+ this.listUser.setListData(users);
+ } else if (line.startsWith("/STATUS")) {
+ this.textAreaStatusLine.setText(line.substring(7));
+ } else if (line.startsWith("/ERROR")) {
+ this.textAreaStatusLine.setText("+++ " + line.substring(7));
+ } else {
+ this.talkListModel.addElement(line);
+ this.listTalk
+ .ensureIndexIsVisible(this.talkListModel.getSize() - 1);
+ }
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+ */
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ if (e.getSource() == this.timer) {
+ final List<String> answer = this.sendAndReceive("/UPDATE");
+ if (!answer.isEmpty()) {
+ handleAnswer(answer);
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package de.hamatoma.jatty;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Internationalization: translations for the UI texts.
+ *
+ * @author hm
+ *
+ */
+public class I18N {
+ static public final int USAGE = 0;
+ static public final int USAGE2 = 1;
+ static public final int USAGE3 = 2;
+ static public final int USAGE4 = 3;
+ static public final int USAGE5 = 4;
+ static public final int USAGE6 = 5;
+ static public final int USAGE7 = 6;
+ static public final int MAIN1 = 7;
+ static public final int SERVER1 = 8;
+ static public final int USAGE8 = 9;
+ static public final int WELCOME = 10;
+ static public final int WELCOME2 = 11;
+ static public final int USERNAME = 12;
+
+ static I18N classLoader = new I18N();
+ static List<String> translations = new ArrayList<String>();
+
+ /**
+ * Reads the translations from a resource.
+ *
+ * @param prefix
+ * prefix of the file with relative path. Will be completed by
+ * the locale and ".properties"
+ * @param locale
+ * the locale shortcut, e.g. "de" or "en-us"
+ * @return <code>true</code>: the resource has been read
+ */
+ public static boolean switchLanguage(final String prefix,
+ final String locale) {
+ boolean rc = false;
+ final String fn = "/" + prefix + "." + locale + ".properties";
+ BufferedReader reader = null;
+ try {
+ final InputStream input = classLoader.getClass()
+ .getResourceAsStream(fn);
+ if (input != null) {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+ if (reader != null) {
+ translations.clear();
+ String line;
+ try {
+ while ((line = reader.readLine()) != null) {
+ if (line.matches("^\\d+=.*")) {
+ translations
+ .add(line.substring(line.indexOf('=') + 1));
+ }
+ }
+ rc = true;
+ System.out
+ .println("successfully loaded: " + fn.substring(1));
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ } catch (final UnsupportedEncodingException e) {
+ e.printStackTrace();
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return rc;
+ }
+
+ /**
+ * Translates a message.
+ *
+ * @param index
+ * index of the message
+ * @param message
+ * default value of the message
+ * @return the translated message or - if not found - the default message
+ */
+ public static String tr(final int index, final String message) {
+ final String rc;
+ if (index < translations.size())
+ rc = translations.get(index);
+ else
+ rc = message;
+ return rc;
+ }
+}
--- /dev/null
+/**\r
+ *\r
+ */\r
+package de.hamatoma.jatty;\r
+\r
+import java.util.Locale;\r
+\r
+/**\r
+ * Starts a chat server or a chat client depending on the program arguments.\r
+ *\r
+ * @author wk\r
+ *\r
+ */\r
+public class Jatty {\r
+ public static final String VERSION = "v1.0.0 (2016.03.23)";\r
+\r
+ /**\r
+ * Extract the address from an argument.\r
+ *\r
+ * @param arg\r
+ * the argument. Syntax: <address>[:<port>]<br>\r
+ * If <code>null "localhost"</code> is returned\r
+ * @return the address of the IP expression\r
+ */\r
+ static String getAddr(final String arg) {\r
+ String rc = arg;\r
+ final int pos = arg == null ? -1 : arg.indexOf(':');\r
+ if (pos >= 0)\r
+ rc = arg.substring(0, pos);\r
+ return rc;\r
+ }\r
+\r
+ /**\r
+ * Extract the port from an argument.\r
+ *\r
+ * @param arg\r
+ * the argument. Syntax: <address>[:<port>]<br>\r
+ * If <code>null "localhost"</code> is returned\r
+ * @return the address of the IP expression\r
+ */\r
+ static int getPort(final String arg) {\r
+ int port = 58123;\r
+ final int pos = arg == null ? -1 : arg.indexOf(':');\r
+ if (pos >= 0)\r
+ port = Integer.parseInt(arg.substring(pos + 1));\r
+ return port;\r
+ }\r
+\r
+ /**\r
+ * Writes a message to the output media.\r
+ *\r
+ * @param message message to write\r
+ */\r
+ public static void log(final String message) {\r
+ System.out.println(message);\r
+ }\r
+\r
+ /**\r
+ * Main program.\r
+ *\r
+ * Evaluates the program arguments and start a server or a client.\r
+ *\r
+ * @param args\r
+ * the program arguments\r
+ */\r
+ public static void main(final String[] args) {\r
+ if (args.length == 0) {\r
+ usage(I18N.tr(I18N.MAIN1, "missing mode:")\r
+ + " server | client | clientcl");\r
+ } else {\r
+ int pos0 = 0;\r
+ String arg0 = args[pos0];\r
+ String locale = Locale.getDefault().getLanguage();\r
+ I18N.switchLanguage("jatty", locale);\r
+ while (arg0 != null && arg0.startsWith("-")) {\r
+ if (arg0.startsWith("--locale=")) {\r
+ locale = arg0.substring(9);\r
+ if (!I18N.switchLanguage("jatty", locale)) {\r
+ log("+++ not supported locale " + locale\r
+ + ". Try --locale=de");\r
+ }\r
+ } else {\r
+ usage("unknown option: " + arg0);\r
+ }\r
+ pos0++;\r
+ arg0 = args.length < pos0 ? args[pos0] : null;\r
+ }\r
+ if (arg0 == null) {\r
+ usage(I18N.tr(I18N.MAIN1, "missing mode:")\r
+ + " server | client | clientcl");\r
+ } else {\r
+ final Scrambler scrambler = new Scrambler("Modern Times",\r
+ "Juwayakani", "2*5^8/3%");\r
+ final String arg1 = pos0 + 1 >= args.length ? null\r
+ : args[pos0 + 1];\r
+ if (arg0.equals("client")) {\r
+ final Client client = new ClientGui(getAddr(arg1),\r
+ getPort(arg1), scrambler);\r
+ client.run();\r
+ } else if (arg0.equals("clientcl")) {\r
+ final Client client = new Client(getAddr(arg1),\r
+ getPort(arg1), scrambler);\r
+ client.run();\r
+ } else if (arg0.equals("server")) {\r
+ final int port = arg1 == null ? getPort(null)\r
+ : Integer.parseInt(arg1);\r
+ final Server server = new Server(port, scrambler);\r
+ server.run();\r
+ } else {\r
+ usage("unknown mode: " + arg0);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Prints a "usage" message.\r
+ *\r
+ * @param message\r
+ * <code>null</code> or the error message\r
+ */\r
+ public static void usage(final String message) {\r
+ System.out.println(I18N.tr(I18N.USAGE, "Usage:")\r
+ + " jatty <opts> { server <port> | { client | clientcl } [<ip>[:<port>]] }\n"\r
+ + I18N.tr(I18N.USAGE2,\r
+ "Starts a chat server, a GUI chat client or a commandline chat client.")\r
+ + "\n" + "<ip>\n\t"\r
+ + I18N.tr(I18N.USAGE3,\r
+ "address of the chat server, e.g. localhost or")\r
+ + " 192.168.3.4\n" + " " + I18N.tr(I18N.USAGE4, "Default:")\r
+ + " localhost\n" + "<port>\n\t"\r
+ + I18N.tr(I18N.USAGE5, "port of the server.") + "\n\t"\r
+ + I18N.tr(I18N.USAGE6, "Default:") + "58123\n"\r
+ + "<opts>:\n--locale=<locale>\n\t"\r
+ + I18N.tr(I18N.USAGE8,\r
+ "language of the GUI. Default: system setting")\r
+ + I18N.tr(I18N.USAGE7, "Examples:") + "\n" + "jatty server\n"\r
+ + "jatty --locale=de server 8888\n"\r
+ + "jatty client localhost:8888\n"\r
+ + "jatty --locale=de client 123.4.3.55\n"\r
+ + "jatty clientcl 194.168.22.4:8888");\r
+ if (message != null)\r
+ System.out.println("+++ " + message);\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * \r
+ */\r
+package de.hamatoma.jatty;\r
+\r
+/**\r
+ * @author wk\r
+ *\r
+ */\r
+public class JattyDb {\r
+ public JattyDb(){\r
+ \r
+ }\r
+ public void initTables(){\r
+ \r
+ }\r
+}\r
--- /dev/null
+/**
+ *
+ */
+package de.hamatoma.jatty;
+
+/**
+ *
+ * Encoding/decoding of strings.
+ *
+ * @author hm
+ *
+ */
+public class Scrambler implements Cloneable {
+ static long[] primes = { //
+ 4794328877860651L, // 11086aadb3872b
+ 4958110297415603L, // 119d600308dfb3
+ 4623102848326049L, // 106cb00308d9a1
+ 4891108807597633L, // 1160700308de41
+ 4578435188447437L, // 1044100308d8cd
+ 5546234485817489L, // 13b445585e4091
+ 5389897676242243L, // 132615585e3d43
+ 4786884267880873L, // 1101a5585e31a9
+ 5241005476646707L, // 129eaaadb38f33
+ 4734771998022511L, // 10d2400308db6f
+ 5278228526545621L, // 12c085585e3ad5
+ 4652881288245079L, // 1087c5585e2f57
+ 5181448596808493L, // 1268800308e32d
+ 4920887247516739L, // 117b85585e3443
+ 5360119236323197L, // 130b000308e77d
+ 5471788386019861L, // 1370900308ea15
+ 4645436678265319L, // 1081000308d9e7
+ 4481655258710579L, // fec0aadb38233
+ };
+ public static void log(final String text) {
+ System.out.println(text);
+ }
+ public static void main(final String[] args) {
+ final Scrambler scrambler = new Scrambler("a", "b", "c");
+ test("What a wonderful world!", scrambler);
+ test(
+ ",.-;:_^\\!\"§$%&/()=?<>|ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrstuvwxyz 1234567890ßöäüÖÄÜ",
+ scrambler);
+ String text = "";
+ for (int ix = 0; ix < 5; ix++) {
+ text = "xxx" + (char) ('a' + ix);
+ log(String.format("%s: %x", text, scrambler.hash(text)));
+ }
+ text = "";
+ for (int ix = 0; ix < 5; ix++) {
+ text = text + "a";
+ log(String.format("%6s: %x", text, scrambler.hash(text)));
+ }
+ for (int ix = 0; ix < 5; ix++) {
+ text = "" + (char) ('a' + ix);
+ log(String.format("%s: %x", text, scrambler.hash(text)));
+ }
+ for (int ix = 0; ix < 5; ix++) {
+ text = (char) ('a' + ix) + "xxx";
+ log(String.format("%s: %x", text, scrambler.hash(text)));
+ }
+ scrambler.setSeed(0);
+ for (int ix = 0; ix < 5; ix++) {
+ log(String.format("%d: %x", ix, scrambler.nextLong()));
+ }
+ log("");
+ }
+ private static void test(final String text, final Scrambler scrambler) {
+ final String text2 = scrambler.encode(text);
+ final String text3 = scrambler.decode(text2);
+ if (!text.equals(text3))
+ log("not equal:\n" + text + "\n" + text3);
+
+ }
+ int callNumber = 0;
+ long factor = 7;
+ long mask = 0xabcd2233L;
+ long offset = 0x123321293456545L;
+ long savedSeed;
+ long seed = 0x19580312;
+ long trueRandomSeed = System.currentTimeMillis()
+ ^ (primes[0]);
+
+ /**
+ * Constructor.
+ *
+ * @param password1
+ * first pass phrase
+ * @param password2
+ * second pass phrase
+ * @param password3
+ * 3rd pass phrase
+ */
+ public Scrambler(final String password1, final String password2,
+ final String password3) {
+ if ((this.factor = hash(password1)) == 0)
+ this.factor = 0x17023947a2b3e477L;
+ this.offset = hash(password2);
+ this.mask = hash(password3);
+ // use an unitialized value:
+ final long random = Runtime.getRuntime().freeMemory();
+ this.trueRandomSeed += primes[(int) (random % 0xffffffffL)
+ % primes.length] ^ Runtime.getRuntime().maxMemory();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ /**
+ * Decodes a string encoded by <code>encode()</code>.
+ *
+ * @param text
+ * encoded text
+ * @return the clear text
+ */
+ public String decode(final String text) {
+ final StringBuilder rc = new StringBuilder();
+ setSeed(text.charAt(0), text.charAt(1), text.charAt(2), text.charAt(3));
+ for (int ix = 4; ix < text.length(); ix++){
+ int cc = text.charAt(ix);
+ if (cc >= 32) {
+ final int offset = nextInt(0, 256);
+ cc = cc - 32 - offset;
+ if (cc < 0)
+ cc += (0x1000 + 32);
+ cc += 32;
+ }
+ rc.append((char) cc);
+ }
+ return rc.toString();
+ }
+
+ /**
+ * Encodes a string.
+ *
+ * @param text
+ * clear text
+ * @return the encoded text
+ */
+ public String encode(final String text) {
+ final StringBuilder rc = new StringBuilder();
+ // salt: 4 char:
+ switchToTrueRandom(true);
+ rc.append((char) nextInt(32, 256)).append((char) nextInt(32, 256));
+ rc.append((char) nextInt(32, 256)).append((char) nextInt(32, 256));
+ switchToTrueRandom(false);
+ setSeed(rc.charAt(0), rc.charAt(1), rc.charAt(2), rc.charAt(3));
+ for (int ix = 0; ix < text.length(); ix++){
+ int cc = text.charAt(ix);
+ if (cc >= 32) {
+ final int offset = nextInt(0, 256);
+ cc = 32 + ((cc - 32 + offset) % (0x10000 - 32));
+ }
+ rc.append((char) cc);
+ }
+ return rc.toString();
+ }
+
+ /**
+ * Converts a string into a long value.
+ *
+ * @param text
+ * text to convert
+ * @return a long value
+ */
+ public long hash(final String text) {
+ long rc = primes[0];
+ int ixPrimes = primes.length - 1;
+ for (int ix = text.length() - 1; ix >= 0; ix--) {
+ rc = (((rc << 1) | (rc >> 63)) + primes[ixPrimes] * text.charAt(ix)
+ + primes[ixPrimes - 1]
+ + primes[text.charAt(ix == 0 ? 0 : ix - 1) % primes.length])
+ ^ (primes[(text.length() - ix) % primes.length]);
+ if ((ixPrimes -= 2) <= 0)
+ ixPrimes = primes.length - 1;
+ }
+ return rc;
+ }
+
+ /**
+ * Returns a pseudo random value in a given range.
+ *
+ * @param min
+ * the minimum value (inclusive)
+ * @param max
+ * the maximum value (exclusive)
+ * @return a pseudo random value x with <code>min <= x < max</code>
+ */
+ public int nextInt(final int min, final int max) {
+ final int rc = min + (int) ((nextLong() >> 12) % (max - min + 1));
+ return rc;
+
+ }
+
+ /**
+ * Returns the next pseudo random value.
+ *
+ * @return a pseudo random value as long value (always not negative)
+ */
+ public long nextLong() {
+ this.seed = this.seed * this.factor + this.offset;
+ this.seed = (this.seed << 41) | (this.seed >> (64 - 41));
+ final long rc = ((this.seed ^ this.mask)
+ ^ primes[++this.callNumber % primes.length]) & 0x7fffffffffffffL;
+ return rc;
+ }
+
+ /**
+ * Resets the pseudo random generator.
+ *
+ * @param seed1
+ * first seed value
+ * @param seed2
+ * 2nd seed value
+ * @param seed3
+ * 3rd seed value
+ * @param seed4
+ * 4th seed value
+ */
+ public void setSeed(final int seed1, final int seed2, final int seed3,
+ final int seed4) {
+ this.callNumber = 0;
+ setSeed((((long) seed1) << 56) | (((long) seed2) << 48)
+ | (((long) seed1) << 40) | (((long) seed1) << 32)
+ | (((long) seed1 + seed2) << 24) | (((long) seed2 + seed3) << 16)
+ | (((long) seed3 + seed4) << 8) + (seed1 + seed2 + seed3 + seed4));
+ }
+
+ /**
+ * Resets the pseudo random generator.
+ */
+ public void setSeed(final long seed) {
+ this.seed = primes[8] ^ seed;
+ }
+
+ /**
+ * Switch random generator from pseudo to true or vice versa.
+ *
+ * @param nowTrueRandom
+ * <code>true</code>: the random generator will be return "true"
+ * random now
+ */
+ private void switchToTrueRandom(final boolean nowTrueRandom) {
+ if (nowTrueRandom) {
+ this.savedSeed = this.seed;
+ this.trueRandomSeed = (this.trueRandomSeed << 17)
+ + System.currentTimeMillis()
+ + (this.trueRandomSeed >> (63 - 17));
+ this.seed = this.trueRandomSeed;
+ } else {
+ this.seed = this.savedSeed;
+ }
+
+ }
+}
--- /dev/null
+/**\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