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


Reply via email to