Author: bdonlan
Date: 2005-05-23 18:56:27 -0400 (Mon, 23 May 2005)
New Revision: 715
Added:
trunk/clients/Javer2/src/org/haverdev/haver/server/
trunk/clients/Javer2/src/org/haverdev/haver/server/AcceptLoop.java
trunk/clients/Javer2/src/org/haverdev/haver/server/Channel.java
trunk/clients/Javer2/src/org/haverdev/haver/server/Entity.java
trunk/clients/Javer2/src/org/haverdev/haver/server/Lobby.java
trunk/clients/Javer2/src/org/haverdev/haver/server/User.java
trunk/clients/Javer2/src/org/haverdev/haver/server/UserConnection.java
Modified:
trunk/clients/Javer2/nbproject/project.properties
Log:
Yet another haver server, huzzah!
Modified: trunk/clients/Javer2/nbproject/project.properties
===================================================================
--- trunk/clients/Javer2/nbproject/project.properties 2005-05-23 21:06:45 UTC
(rev 714)
+++ trunk/clients/Javer2/nbproject/project.properties 2005-05-23 22:56:27 UTC
(rev 715)
@@ -36,7 +36,7 @@
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
-main.class=org.haverdev.javer2.CMod
+main.class=org.haverdev.haver.server.AcceptLoop
manifest.file=manifest.mf
platform.active=Java_HotSpot_TM__Client_VM_1.4.2_08-b03
run.classpath=\
Added: trunk/clients/Javer2/src/org/haverdev/haver/server/AcceptLoop.java
===================================================================
--- trunk/clients/Javer2/src/org/haverdev/haver/server/AcceptLoop.java
2005-05-23 21:06:45 UTC (rev 714)
+++ trunk/clients/Javer2/src/org/haverdev/haver/server/AcceptLoop.java
2005-05-23 22:56:27 UTC (rev 715)
@@ -0,0 +1,42 @@
+/*
+ * AcceptLoop.java
+ *
+ * Created on May 23, 2005, 5:39 PM
+ */
+
+package org.haverdev.haver.server;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ *
+ * @author bdonlan
+ */
+public class AcceptLoop extends Thread {
+
+ ServerSocket socket;
+
+ /** Creates a new instance of AcceptLoop */
+ public AcceptLoop(int port) throws IOException {
+ socket = new ServerSocket(port);
+ }
+
+ public void run() {
+ try {
+ while (true) {
+ synchronized (socket) {
+ if (socket.isClosed()) return;
+ }
+ Socket s = socket.accept();
+ new UserConnection(s);
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) throws Throwable {
+ new AcceptLoop(15678).start();
+ }
+}
Property changes on:
trunk/clients/Javer2/src/org/haverdev/haver/server/AcceptLoop.java
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/clients/Javer2/src/org/haverdev/haver/server/Channel.java
===================================================================
--- trunk/clients/Javer2/src/org/haverdev/haver/server/Channel.java
2005-05-23 21:06:45 UTC (rev 714)
+++ trunk/clients/Javer2/src/org/haverdev/haver/server/Channel.java
2005-05-23 22:56:27 UTC (rev 715)
@@ -0,0 +1,19 @@
+/*
+ * Channel.java
+ *
+ * Created on May 23, 2005, 5:51 PM
+ */
+
+package org.haverdev.haver.server;
+
+/**
+ *
+ * @author bdonlan
+ */
+public interface Channel extends Entity {
+ public boolean contains(String namespace, String name);
+ public boolean contains(Entity what);
+
+ public Entity[] getContents();
+ public String[] getNames(String namespace);
+}
Property changes on:
trunk/clients/Javer2/src/org/haverdev/haver/server/Channel.java
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/clients/Javer2/src/org/haverdev/haver/server/Entity.java
===================================================================
--- trunk/clients/Javer2/src/org/haverdev/haver/server/Entity.java
2005-05-23 21:06:45 UTC (rev 714)
+++ trunk/clients/Javer2/src/org/haverdev/haver/server/Entity.java
2005-05-23 22:56:27 UTC (rev 715)
@@ -0,0 +1,16 @@
+/*
+ * Thing.java
+ *
+ * Created on May 23, 2005, 5:51 PM
+ */
+
+package org.haverdev.haver.server;
+
+/**
+ *
+ * @author bdonlan
+ */
+public interface Entity {
+ public String getNamespace();
+ public String getName();
+}
Property changes on:
trunk/clients/Javer2/src/org/haverdev/haver/server/Entity.java
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/clients/Javer2/src/org/haverdev/haver/server/Lobby.java
===================================================================
--- trunk/clients/Javer2/src/org/haverdev/haver/server/Lobby.java
2005-05-23 21:06:45 UTC (rev 714)
+++ trunk/clients/Javer2/src/org/haverdev/haver/server/Lobby.java
2005-05-23 22:56:27 UTC (rev 715)
@@ -0,0 +1,87 @@
+/*
+ * Lobby.java
+ *
+ * Created on May 23, 2005, 6:34 PM
+ */
+
+package org.haverdev.haver.server;
+import java.util.*;
+
+/**
+ *
+ * @author bdonlan
+ */
+public final class Lobby implements Channel {
+
+ public static final Lobby theLobby = new Lobby();
+
+ HashSet everything = new HashSet();
+ HashMap namespaces = new HashMap();
+
+ /** There can be only one */
+ private Lobby() {
+ }
+
+ public static final String[] emptyArray = {};
+
+ public synchronized String[] getNames(String namespace) {
+ HashMap subset = (HashMap)namespaces.get(namespace.toLowerCase());
+ if (subset == null)
+ return emptyArray;
+ return (String[])subset.keySet().toArray();
+ }
+
+ public synchronized boolean contains(Entity what) {
+ return everything.contains(what);
+ }
+
+ public String getNamespace() {
+ return "channel";
+ }
+
+ public String getName() {
+ return "&lobby";
+ }
+
+ public synchronized Entity[] getContents() {
+ return (Entity[])everything.toArray();
+ }
+
+ public synchronized boolean contains(String namespace, String name) {
+ HashMap subset = (HashMap)namespaces.get(namespace.toLowerCase());
+ if (subset == null)
+ return false;
+ return subset.containsKey(name.toLowerCase());
+ }
+
+ public synchronized Entity lookup(String namespace, String name) {
+ HashMap subset = (HashMap)namespaces.get(namespace.toLowerCase());
+ if (subset == null)
+ return null;
+ return (Entity)subset.get(name.toLowerCase());
+ }
+
+ public synchronized void register(Entity e) {
+ String namespace = e.getNamespace().toLowerCase();
+ String name = e.getName().toLowerCase().intern();
+ if (contains(e))
+ return;
+ HashMap subset = (HashMap)namespaces.get(namespace);
+ if (subset == null) {
+ subset = new HashMap();
+ namespaces.put(namespace.intern(), subset);
+ }
+ subset.put(name, e);
+ everything.add(e);
+ }
+
+ public synchronized void unregister(Entity e) {
+ String namespace = e.getNamespace().toLowerCase();
+ String name = e.getName().toLowerCase();
+ if (!contains(e)) return;
+ HashMap subset = (HashMap)namespaces.get(namespace);
+ subset.remove(name);
+ everything.remove(e);
+ }
+
+}
Property changes on:
trunk/clients/Javer2/src/org/haverdev/haver/server/Lobby.java
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/clients/Javer2/src/org/haverdev/haver/server/User.java
===================================================================
--- trunk/clients/Javer2/src/org/haverdev/haver/server/User.java
2005-05-23 21:06:45 UTC (rev 714)
+++ trunk/clients/Javer2/src/org/haverdev/haver/server/User.java
2005-05-23 22:56:27 UTC (rev 715)
@@ -0,0 +1,19 @@
+/*
+ * User.java
+ *
+ * Created on May 23, 2005, 5:54 PM
+ */
+
+package org.haverdev.haver.server;
+
+/**
+ *
+ * @author bdonlan
+ */
+public interface User extends Entity {
+ public void sendPrivateMessage(String from, String[] args);
+ public void notifyPublicMessage(String channel, String from, String[]
args);
+ public void notifyJoin(String channel, Entity what);
+ public void notifyPart(String channel, Entity what);
+ public void notifyQuit(String who, String type, String detail);
+}
Property changes on:
trunk/clients/Javer2/src/org/haverdev/haver/server/User.java
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/clients/Javer2/src/org/haverdev/haver/server/UserConnection.java
===================================================================
--- trunk/clients/Javer2/src/org/haverdev/haver/server/UserConnection.java
2005-05-23 21:06:45 UTC (rev 714)
+++ trunk/clients/Javer2/src/org/haverdev/haver/server/UserConnection.java
2005-05-23 22:56:27 UTC (rev 715)
@@ -0,0 +1,353 @@
+/*
+ * UserConnection.java
+ *
+ * Created on May 23, 2005, 5:57 PM
+ */
+
+package org.haverdev.haver.server;
+import org.haverdev.haver.NonblockingOutputStream;
+import java.io.*;
+import java.util.*;
+import java.net.Socket;
+import java.lang.reflect.*;
+
+/**
+ *
+ * @author bdonlan
+ */
+public class UserConnection {
+
+ Socket sock;
+ NonblockingOutputStream nws;
+ PrintWriter writer;
+ BufferedReader reader;
+ Thread readThread;
+ UserCommandContext context = new InitContext();
+ UserEntity e = null;
+
+ /**
+ * Reverses Haver escaping.
+ * @param val String to unescape
+ * @return The unescaped string
+ */
+ protected static final String unescape(String val) {
+ //if (val != null) return val;
+ StringBuffer out = new StringBuffer();
+ int pin = 0;
+ int nextEsc = -1;
+ while (-1 != (nextEsc = val.indexOf("\033", pin))) {
+ out.append(val.substring(0, nextEsc - 1));
+ switch (val.charAt(nextEsc + 1)) {
+ case 'r':
+ out.append('\r');
+ break;
+ case 'n':
+ out.append('\n');
+ break;
+ case 'e':
+ out.append('\033');
+ break;
+ case 't':
+ out.append('\t');
+ break;
+ default:
+ out.append(val.charAt(nextEsc + 1));
+ break;
+ }
+ pin = nextEsc + 2;
+ }
+ out.append(val.substring(pin));
+ return out.toString();
+ }
+
+ /**
+ * Escapes a string according to the Haver protocol
+ * @param val String to escape
+ * @return Escaped string
+ */
+ protected static final String escape(String val) {
+ return val.replaceAll("\033", "\033e")
+ .replaceAll("\n", "\033n")
+ .replaceAll("\r", "\033r")
+ .replaceAll("\t", "\033t");
+ }
+
+ /**
+ * Decodes a raw line from the Haver protocol.
+ * @param inLine Line to decode. May end in '\r\n' but must only be one
line.
+ * @return An array containing the unescaped arguments of the line.
+ */
+ protected static final String[] decodeLine(String inLine) {
+ inLine = inLine.replaceAll("[\n\r]", ""); // We'll assume you only
passed one
+ // line in.
+ String[] args = inLine.split("\t");
+ for (int i = 0; i < args.length; i++) {
+ args[i] = unescape(args[i]);
+ }
+ return args;
+ }
+
+ /**
+ * Encodes a set of arguments for line transmission
+ * @param args Unescaped arguments to encode
+ * @return Escaped and joined line ready for transmission, complete with
ending \r\n
+ */
+ protected static final String encodeLine(String[] args) {
+ StringBuffer out = new StringBuffer();
+ for (int i = 0; i < args.length; i++) {
+ if (i != 0)
+ out.append("\t");
+ out.append(escape(args[i]));
+ }
+ out.append("\r\n");
+ return out.toString();
+ }
+
+ /** Creates a new instance of UserConnection */
+ public UserConnection(Socket s) throws IOException {
+ sock = s;
+ nws = new NonblockingOutputStream(new
BufferedOutputStream(s.getOutputStream()));
+ writer = new PrintWriter(new OutputStreamWriter(nws));
+ reader = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
+
+ readThread = new Thread(new Runnable() {
+ public void run() {
+ inputLoop();
+ }
+ });
+ readThread.start();
+ }
+
+ public static final String[] greeting = {"HAVER",
"org.haverdev.haver.server/0.1"};
+
+ public void inputLoop() {
+ try {
+ while (!sock.isClosed()) {
+ String line = reader.readLine();
+ if (line == null) {
+ throw new IOException("Disconnected.");
+ }
+ try {
+ processLine(line);
+ }
+ catch (PropagatedException e) {
+ e.clientReport();
+ }
+ catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ } catch (IOException e) {
+ ioExcept(e);
+ }
+ }
+
+ protected void ioExcept(IOException e) {
+ e.printStackTrace();
+ try { writer.close(); } catch (Throwable t) { }
+ try { sock.close(); } catch (Throwable t) { }
+ }
+
+ public synchronized void sendLine(String[] args) {
+ String line = encodeLine(args);
+ writer.print(line);
+ writer.flush();
+ }
+
+ public synchronized void processLine(String line) throws
PropagatedException {
+ String[] cmd = decodeLine(line);
+ context.processCommand(cmd);
+ }
+
+ class UserEntity implements User {
+ String name;
+
+ UserEntity(String name) {
+ this.name = name.intern();
+ }
+
+ public void notifyPart(String channel, Entity what) {
+ String[] msg = {"PART", what.getName(), channel};
+ sendLine(msg);
+ }
+
+ public void notifyJoin(String channel, Entity what) {
+ String[] msg = {"JOIN", what.getName(), channel};
+ sendLine(msg);
+ }
+
+ public void notifyPublicMessage(String channel, String from, String[]
args) {
+ String[] msg = new String[args.length + 3];
+ msg[0] = "IN";
+ msg[1] = channel;
+ msg[2] = from;
+ System.arraycopy(args, 0, msg, 3, args.length);
+ sendLine(msg);
+ }
+
+ public final String getNamespace() {
+ return "user";
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void sendPrivateMessage(String from, String[] args) {
+ String[] msg = new String[args.length + 2];
+ msg[0] = "FROM";
+ msg[1] = from;
+ System.arraycopy(args, 0, msg, 2, args.length);
+ sendLine(msg);
+ }
+
+ public void notifyQuit(String who, String type, String detail) {
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (!(obj instanceof User))
+ return false;
+ return ((User)obj).getName().equals(name);
+ }
+
+ public String toString() {
+ return "user: " + name;
+ }
+
+ public int hashCode() {
+ return name.hashCode() ^ "user".hashCode();
+ }
+ }
+
+ public abstract class PropagatedException extends Exception {
+ public abstract void clientReport();
+ public PropagatedException(String reason) {
+ super(reason);
+ }
+ }
+
+ public final class UnknownClientCommandException extends
PropagatedException {
+ String cmd;
+ public UnknownClientCommandException(String[] cmd) {
+ super("Unknown command " + cmd[0]);
+ this.cmd = cmd[0];
+ }
+
+ public void clientReport() {
+ String[] fail = {"FAIL", cmd, "unknown.cmd"};
+ sendLine(fail);
+ }
+ }
+
+ public final class InternalCommandException extends PropagatedException {
+ String cmd, reason;
+ public InternalCommandException(String cmd, String reason) {
+ super("Command " + cmd + " failed: " + reason);
+ this.cmd = cmd;
+ this.reason = reason;
+ }
+
+ public InternalCommandException(String cmd, Throwable t) {
+ this(cmd, t.getMessage());
+ initCause(t);
+ }
+
+ public void clientReport() {
+ String[] fail = {"FAIL", cmd, "internal.error", "Internal
exception: " + reason};
+ sendLine(fail);
+ }
+ }
+
+ public class SimplePropagatedException extends PropagatedException {
+ String cmd, code;
+ public SimplePropagatedException(String cmd, String code, String
detail) {
+ super(detail);
+ this.cmd = cmd;
+ this.code = code;
+ }
+
+ public void clientReport() {
+ String[] fail = { "FAIL", cmd, code, this.getMessage() };
+ sendLine(fail);
+ }
+ }
+
+ public final class UserAlreadyExists extends SimplePropagatedException {
+ public UserAlreadyExists(String name) {
+ super("IDENT", "exists.user", "User " + name + " is in use");
+ }
+ }
+
+ public interface UserCommandContext {
+ public void processCommand(String[] cmd) throws PropagatedException;
+ }
+
+ public abstract class UserCommandReflect implements UserCommandContext {
+ public void processCommand(String[] cmd) throws PropagatedException {
+ if (!cmd[0].toUpperCase().equals(cmd[0])) throw new
UnknownClientCommandException(cmd);
+ Class myClass = getClass();
+ Class[] argTypes = {cmd.getClass()};
+ Method m;
+ try {
+ m = myClass.getMethod("handle_" + cmd[0], argTypes);
+ } catch(NoSuchMethodException e) {
+ throw new UnknownClientCommandException(cmd);
+ }
+ Object[] args = {cmd};
+ try {
+ m.invoke(this, args);
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof PropagatedException)
+ throw (PropagatedException)e.getTargetException();
+ e.getTargetException().printStackTrace();
+ throw new InternalCommandException(cmd[0], e);
+ } catch (Throwable t) {
+ throw new InternalCommandException(cmd[0], t);
+ }
+ }
+ }
+
+ public class InitContext extends UserCommandReflect {
+ public void handle_HAVER(String[] args) {
+ System.err.println("Greeting from client: " + args[1]);
+ sendLine(greeting);
+ context = new LoginContext();
+ }
+
+ public void processCommand(String[] cmd) {
+ try {
+ super.processCommand(cmd);
+ } catch (PropagatedException e) {
+ e.printStackTrace();
+ ioExcept(new IOException("Client didn't greet me. I feel
unloved. :("));
+ }
+ }
+ }
+
+ public class LoginContext extends UserCommandReflect {
+ public void handle_IDENT(String[] args) throws PropagatedException {
+ if (Lobby.theLobby.contains("user", args[1]))
+ throw new UserAlreadyExists(args[1]);
+ System.err.println("Hello, " + args[1] + ", nice to meet you.");
+ String[] msg = {"HELLO", args[1]};
+ sendLine(msg);
+ e = new UserEntity(args[1]);
+ Lobby.theLobby.register(e);
+ context = new NormalContext();
+ }
+ }
+
+ public class NormalContext extends UserCommandReflect {
+ public void handle_TO(String[] args) throws PropagatedException {
+ String who = args[1];
+ User them = (User)Lobby.theLobby.lookup("user", who);
+ if (them == null)
+ throw new SimplePropagatedException("TO", "notfound.user",
"User " + who + " does not exist or is not logged in");
+ String margs[] = new String[args.length - 2];
+ System.arraycopy(args, 2, margs, 0, margs.length);
+ them.sendPrivateMessage(e.getName(), margs);
+ }
+ }
+}
Property changes on:
trunk/clients/Javer2/src/org/haverdev/haver/server/UserConnection.java
___________________________________________________________________
Name: svn:eol-style
+ native