Commits

Meikel Brandmeyer committed 18932fe

Extract nailgun-connector into dedicated subproject

Comments (0)

Files changed (50)

     }
 }
 
+project(':nailgun-connector') {
+    apply plugin: 'clojure'
+
+    defaultRepositories {
+        flatDir dirs: project(':').file('lib/build')
+        mavenCentral()
+    }
+
+    dependencies {
+        compile 'org.clojure:clojure:[1.2,1.5)'
+        compile project(':server')
+    }
+
+    uploadArchives {
+        clojarsDeploy()
+    }
+
+    compileClojure.dependsOn compileJava
+}
+
 project(':nrepl-client') {
     task compileHaskell {
         //inputs.dir project.file('src/main/haskell')
 task runNailgun(type: JavaExec) {
     main = "vimclojure.nailgun.NGServer"
     args = [ "127.0.0.1" ]
-    dependsOn project(':server').classes
-    dependsOn project(':server').processResources
+    dependsOn project(':nailgun-connector').classes
+    dependsOn project(':nailgun-connector').processResources
     classpath = project.files(
         project(':server').sourceSets.main.clojure.srcDirs,
-        project(':server').sourceSets.main.output.classesDir,
-        project(':server').sourceSets.main.output.resourcesDir,
-        project(':server').sourceSets.main.compileClasspath
+        project(':nailgun-connector').sourceSets.main.clojure.srcDirs,
+        project(':nailgun-connector').sourceSets.main.output.classesDir,
+        project(':nailgun-connector').sourceSets.main.output.resourcesDir,
+        project(':nailgun-connector').sourceSets.main.compileClasspath
     )
 }
 

nailgun-connector/src/main/clojure/vimclojure/connector/nailgun.clj

+;-
+; Copyright 2012 (c) Meikel Brandmeyer.
+; All rights reserved.
+;
+; Permission is hereby granted, free of charge, to any person obtaining a copy
+; of this software and associated documentation files (the "Software"), to deal
+; in the Software without restriction, including without limitation the rights
+; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+; copies of the Software, and to permit persons to whom the Software is
+; furnished to do so, subject to the following conditions:
+;
+; The above copyright notice and this permission notice shall be included in
+; all copies or substantial portions of the Software.
+;
+; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+; THE SOFTWARE.
+
+(ns vimclojure.connector.nailgun
+  (:require
+    vimclojure.nails
+    [vimclojure.util :as util])
+  (:import
+    java.io.BufferedReader
+    java.io.ByteArrayOutputStream
+    java.io.InputStreamReader
+    java.io.OutputStreamWriter
+    java.io.PrintStream
+    java.io.PrintWriter
+    clojure.lang.LineNumberingPushbackReader
+    vimclojure.nailgun.NGContext
+    vimclojure.nailgun.NGServer
+    vimclojure.nailgun.ThreadLocalInputStream
+    vimclojure.nailgun.ThreadLocalPrintStream))
+
+(defn start-server-thread
+  "Start a nailgun server in a dedicated daemon thread. Host defaults
+  to 127.0.0.1, port to 2113."
+  ([]     (start-server-thread "127.0.0.1" 2113))
+  ([host] (start-server-thread host 2113))
+  ([host port]
+   (doto (Thread. #(NGServer/main (into-array [(str host ":" port)])))
+     (.setDaemon true)
+     (.start))))
+
+(defn- make-stream-set
+  [in out err encoding]
+  [(-> in (InputStreamReader. encoding) LineNumberingPushbackReader.)
+   (-> out (OutputStreamWriter. encoding))
+   (-> err (OutputStreamWriter. encoding) PrintWriter.)])
+
+(defn- set-input-stream
+  [#^ThreadLocalInputStream sys local]
+  (let [old (.getInputStream sys)]
+    (.init sys local)
+    old))
+
+(defn- set-output-stream
+  [#^ThreadLocalPrintStream sys local]
+  (let [old (.getPrintStream sys)]
+    (.init sys local)
+    old))
+
+(defn nailgun-driver
+  "Entry point for the nailgun connector."
+  [#^NGContext ctx]
+  (let [args         (.getArgs ctx)
+        nail         (aget args 0)
+        nail         (let [slash (.indexOf nail "/")
+                           [nspace nail] (if (neg? slash)
+                                           ["vimclojure.nails" nail]
+                                           [(subs nail 0 slash) (subs nail slash)])]
+                       @(resolve (symbol nspace nail)))
+        out          (ByteArrayOutputStream.)
+        err          (ByteArrayOutputStream.)
+        encoding     (System/getProperty "clojure.vim.encoding" "UTF-8")
+        [clj-in clj-out clj-err] (make-stream-set (.in ctx) out err encoding)
+        sys-in       (set-input-stream System/in (.in ctx))
+        sys-out      (set-output-stream System/out (PrintStream. out))
+        sys-err      (set-output-stream System/err (PrintStream. err))
+        result       (binding [*in*  clj-in
+                               *out* clj-out
+                               *err* clj-err]
+                       (try
+                         (nail (next args))
+                         (catch Throwable e
+                           (.printStackTrace e))))]
+    (.flush clj-out)
+    (.flush clj-err)
+    (set-input-stream System/in sys-in)
+    (set-output-stream System/out sys-out)
+    (set-output-stream System/err sys-err)
+    (let [output (.getBytes
+                   (print-str
+                     (util/clj->vim
+                       {:value  result
+                        :stdout (.toString out encoding)
+                        :stderr (.toString err encoding)}))
+                   encoding)]
+      (.write (.out ctx) output 0 (alength output)))
+    (.flush (.out ctx))))

nailgun-connector/src/main/java/org/apache/tools/ant/ExitException.java

+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowlegement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "Ant" and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package org.apache.tools.ant;
+
+/**
+ * Used to report exit status of classes which call System.exit().
+ *
+ * @see org.apache.tools.ant.util.optional.NoExitSecurityManager
+ * @see org.apache.tools.ant.types.Permissions
+ *
+ * @author Conor MacNeill
+ * @author <a href="mailto:martijn@kruithof.xs4all.nl">Martijn Kruithof</a>
+ */
+public class ExitException extends SecurityException {
+
+    /** Status code */
+    private int status;
+
+    /**
+     * Constructs an exit exception.
+     * @param status the status code returned via System.exit()
+     */
+    public ExitException(int status) {
+        super("ExitException: status " + status);
+        this.status = status;
+    }
+
+    /**
+     * Constructs an exit exception.
+     * @param msg the message to be displayed.
+     * @param status the status code returned via System.exit()
+     */
+    public ExitException(String msg, int status) {
+        super(msg);
+        this.status = status;
+    }
+    
+    /**
+     * The status code returned by System.exit()
+     *
+     * @return the status code returned by System.exit()
+     */
+    public int getStatus() {
+        return status;
+    }
+}

nailgun-connector/src/main/java/vimclojure/Nail.java

+/*-
+ * Copyright 2009-2011 (c) Meikel Brandmeyer.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package vimclojure;
+
+import clojure.lang.RT;
+import clojure.lang.Symbol;
+import clojure.lang.Var;
+
+import vimclojure.nailgun.NGContext;
+
+public class Nail {
+    /* Load up vimclojure */
+    static Var nailDriver;
+
+    static {
+        try {
+            final Var require = RT.var("clojure.core", "require");
+            require.invoke(Symbol.create("vimclojure.connector.nailgun"));
+
+            nailDriver = RT.var("vimclojure.connector.nailgun", "nailgun-driver");
+        } catch (Exception exc) {
+            Throwable e = exc;
+            System.err.println("A crisis has arisen:");
+            e.printStackTrace();
+        }
+    }
+
+    public static void nailMain(NGContext ctx) throws Exception {
+        nailDriver.invoke(ctx);
+    }
+}

nailgun-connector/src/main/java/vimclojure/nailgun/Alias.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+/**
+ * Provides a means to map memorable, short names to classes in order
+ * to make the issuing of commands more convenient.  For example, an
+ * Alias can map the "<code>mycommand</code>" command to the <code>com.yourdomain.yourpackage.YourClass</code>
+ * class.  Obviously, it's a lot easier to type "<code>ng mycommand</code>" than the fully
+ * qualified class name.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+public class Alias implements Comparable {
+
+	/**
+	 * The alias name
+	 */
+	private String name;
+	
+	/**
+	 * The alias description (may be used to provide help to users)
+	 */
+	private String description;
+	
+	/**
+	 * The class providing a <code>main()</code> or <code>nailMain()</code> method
+	 */
+	private Class clazz;
+	
+	/**
+	 * Creates a new Alias with the specified properties.
+	 * @param name the alias name (short command)
+	 * @param description a description of the command
+	 * @param clazz the class implementing the command
+	 */
+	public Alias(String name, String description, Class clazz) {
+		if (name == null) throw (new IllegalArgumentException("Alias must have a name."));
+		this.name = name.trim();
+		if (this.name.length() == 0) throw (new IllegalArgumentException("Alias must have a name."));
+		
+		if (clazz == null) throw (new IllegalArgumentException("Alias must have an associated class."));
+		this.description = description;
+		this.clazz = clazz;
+	}
+	
+	/**
+	 * Returns the <code>Class</code> object providing a static <code>main()</code> or <code>nailMain()</code> method
+	 * for this command.
+	 * @return the <code>Class</code> object providing a static <code>main()</code> or <code>nailMain()</code> method
+	 * for this command.
+	 */
+	public Class getAliasedClass() {
+		return(clazz);
+	}
+	
+	/**
+	 * Returns the name of the aliased command
+	 * @return the name of the aliased command
+	 */
+	public String getName() {
+		return (name);
+	}
+	
+	/**
+	 * Returns a description for the aliased command
+	 * @return a description for the aliased command
+	 */
+	public String getDescription() {
+		return (description);
+	}
+	
+	/**
+	 * @see Object#hashCode()
+	 */
+	public int hashCode() {
+		return (name.hashCode());
+	}
+	
+	/**
+	 * Checks whether two Aliases have the same name.  Does <b>not</b>
+	 * compare any other fields.
+	 * @param o the other Alias to check
+	 * @return true if the specified Alias has the same name as this Alias.
+	 */
+	public boolean equals(Object o) {
+		return (compareTo(o) == 0);
+	}
+	
+	/**
+	 * Compares Alias <b>names</b> - no other fields are compared.
+	 * @see Comparable#compareTo(Object)
+	 */
+	public int compareTo(Object o) {
+		return (name.compareTo(((Alias) o).getName()));
+	}
+}

nailgun-connector/src/main/java/vimclojure/nailgun/AliasManager.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * An AliasManager is used to store and lookup command Aliases by name.
+ * See <a href="Alias.html">Alias</a> for more details.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+public class AliasManager {
+	
+	/**
+	 * actual alias storage
+	 */
+	private Map aliases;
+	
+	/**
+	 * Creates a new AliasManager, populating it with
+	 * default Aliases.
+	 */
+	public AliasManager() {
+		aliases = new java.util.HashMap();
+		
+		try {
+			Properties props = new Properties();
+			props.load(getClass().getClassLoader().getResourceAsStream("vimclojure/nailgun/builtins/builtins.properties"));
+			loadFromProperties(props);
+		} catch (java.io.IOException e) {
+			System.err.println("Unable to load builtins.properties: " + e.getMessage());
+		}
+	}
+	
+	/**
+	 * Loads Aliases from a java.util.Properties file located at the
+	 * specified URL.  The properties must be of the form:
+	 * <pre><code>[alias name]=[fully qualified classname]</code></pre>
+	 * each of which may have an optional
+	 * <pre><code>[alias name].desc=[alias description]</code></pre>
+	 * 
+	 * For example, to create an alias called "<code>myprog</code>" for
+	 * class <code>com.mydomain.myapp.MyProg</code>, the following properties
+	 * would be defined:
+	 * 
+	 * <pre><code>myprog=com.mydomain.myapp.MyProg
+	 *myprog.desc=Runs my program.
+	 * </code></pre>
+	 * @param properties the Properties to load.
+	 */
+	public void loadFromProperties(java.util.Properties properties) {
+		for (Iterator i = properties.keySet().iterator(); i.hasNext();) {
+			String key = (String) i.next();
+			if (!key.endsWith(".desc")) {
+				try {
+					Class clazz = Class.forName(properties.getProperty(key));
+					String desc = properties.getProperty(key + ".desc", "");
+					addAlias(new Alias(key, desc, clazz));
+				} catch (ClassNotFoundException e) {
+					System.err.println("Unable to locate class " + properties.getProperty(key));
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Adds an Alias, replacing any previous entries with the
+	 * same name.
+	 * @param alias the Alias to add
+	 */
+	public void addAlias(Alias alias) {
+		synchronized (aliases) {
+			aliases.put(alias.getName(), alias);
+		}
+	}
+	
+	/**
+	 * Returns a Set that is a snapshot of the Alias list.
+	 * Modifications to this Set will not impact the AliasManager
+	 * in any way.
+	 * @return a Set that is a snapshot of the Alias list.
+	 */
+	public Set getAliases() {
+		Set result = new java.util.TreeSet();
+		synchronized(aliases) {
+			result.addAll(aliases.values());
+		}
+		return (result);
+	}
+
+	/**
+	 * Removes the Alias with the specified name from the AliasManager.
+	 * If no such Alias exists in this AliasManager, this method has no effect.
+	 * @param aliasName the name of the Alias to remove
+	 */
+	public void removeAlias(String aliasName) {
+		synchronized (aliases) {
+			aliases.remove(aliasName);
+		}
+	}
+
+	/**
+	 * Returns the Alias with the specified name
+	 * @param aliasName the name of the Alias to retrieve
+	 * @return the requested Alias, or null if no such Alias
+	 * is defined in this AliasManager.
+	 */
+	public Alias getAlias(String aliasName) {
+		return ((Alias) aliases.get(aliasName));
+	}
+
+}

nailgun-connector/src/main/java/vimclojure/nailgun/LongUtils.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+*/
+
+package vimclojure.nailgun;
+
+/**
+ * Provides a couple of utility methods that help in reading/writin
+ * chunks of data in the Nailgun protocol.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+class LongUtils {
+
+	/**
+	 * Encodes the specified long in 4 consecutive bytes (big-endian)
+	 * in the specified array, beginning at the specified offset.
+	 * @param l the long to encode
+	 * @param b the array into which the long should be written
+	 * @param offset the offset into the array at which the writing
+	 * should begin
+	 */
+	public static void toArray(long l, byte[] b, int offset) {
+		b[offset + 3] = (byte) (l % 256);
+		l >>>= 8;
+		b[offset + 2] = (byte) (l % 256);
+		l >>>= 8;
+		b[offset + 1] = (byte) (l % 256);
+		l >>>= 8;
+		b[0] = (byte) (l % 256);
+	}
+	
+	/**
+	 * Reads a 4-byte big-endian long from the specified array,
+	 * beginnin at the specified offset.
+	 * @param b the array containing the encoded long
+	 * @param offset the offset into the array at which the encoded
+	 * long begins
+	 * @return the decoded long
+	 */
+	public static long fromArray(byte[] b, int offset) {
+		return(((long) (b[offset] & 0xff) << 24)
+				+ ((long) (b[offset + 1] & 0xff) << 16)
+				+ ((long) (b[offset + 2] & 0xff) << 8)
+				+ ((long) (b[offset + 3] & 0xff)));
+	}
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGConstants.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+import java.util.Properties;
+
+/**
+ * Just a simple holder for various NailGun-related contants.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+public class NGConstants {
+	
+	/**
+	 * The default NailGun port (2113)
+	 */
+	public static final int DEFAULT_PORT = 2113;
+	
+	/**
+	 * The exit code sent to clients if an exception occurred on the server
+	 */
+	public static final int EXIT_EXCEPTION = 899;
+	
+	/**
+	 * The exit code sent to clients if an invalid command is sent
+	 */
+	public static final int EXIT_NOSUCHCOMMAND = 898;
+
+	/**
+	 * Chunk type marker for command line arguments
+	 */
+	public static final char CHUNKTYPE_ARGUMENT = 'A';
+
+	/**
+	 * Chunk type marker for client environment variables
+	 */
+	public static final char CHUNKTYPE_ENVIRONMENT = 'E';
+	
+	/**
+	 * Chunk type marker for the command (alias or class)
+	 */
+	public static final char CHUNKTYPE_COMMAND = 'C';
+	
+	/**
+	 * Chunk type marker for client working directory
+	 */	
+	public static final char CHUNKTYPE_WORKINGDIRECTORY = 'D';
+	
+	/**
+	 * Chunk type marker for stdin
+	 */
+	public static final char CHUNKTYPE_STDIN = '0';
+
+	/**
+	 * Chunk type marker for the end of stdin
+	 */
+	public static final char CHUNKTYPE_STDIN_EOF = '.';
+
+	/**
+	 * Chunk type marker for stdout
+	 */
+	public static final char CHUNKTYPE_STDOUT = '1';
+	
+	/**
+	 * Chunk type marker for stderr
+	 */	
+	public static final char CHUNKTYPE_STDERR = '2';
+	
+	/**
+	 * Chunk type marker for client exit chunks
+	 */	
+	public static final char CHUNKTYPE_EXIT = 'X';
+	
+	/**
+	 * Server version number
+	 */
+	public static final String VERSION;
+	
+	/**
+	 * Loads the version number from a file generated by Ant.
+	 */
+	static {
+		Properties props = new Properties();
+		try {
+			props.load(NGConstants.class.getClassLoader().getResourceAsStream("vimclojure/nailgun/nailgun-version.properties"));
+		} catch (java.io.IOException e) {
+			System.err.println("Unable to load nailgun-version.properties.");
+		}
+		VERSION = props.getProperty("version", "UNKNOWN");
+	}
+	
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGContext.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ */
+
+package vimclojure.nailgun;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Properties;
+
+/**
+ * <p>Provides quite a bit of potentially useful information to classes
+ * specifically written for NailGun. The <a href="NGServer.html">NailGun server</a> itself, its
+ * <a href="AliasManager.html">AliasManager</a>, the remote client's environment variables, and other
+ * information is available via this class. For all intents and purposes,
+ * the NGContext represents a single connection from a NailGun client.</p>
+ * 
+ * If a class is written with a
+ * 
+ * <pre><code>
+ * public static void nailMain(NGContext context)
+ * </code></pre>
+ * 
+ * method, that method will be called by NailGun instead of the traditional
+ * <code>main(String[])</code> method normally used for programs. A fully populated <code>NGContext</code>
+ * object will then be provided to <code>nailMain()</code>.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb </a>
+ */
+public class NGContext {
+
+	/**
+	 * The remote host's environment variables
+	 */
+	private Properties remoteEnvironment = null;
+
+	/**
+	 * The remote host's address
+	 */
+	private InetAddress remoteHost = null;
+
+	/**
+	 * The port on the remote host that is communicating with NailGun
+	 */
+	private int remotePort = 0;
+
+	/**
+	 * Command line arguments for the nail
+	 */
+	private String[] args = null;
+
+	/**
+	 * A stream to which a client exit code can be printed
+	 */
+	private PrintStream exitStream = null;
+
+	/**
+	 * The NGServer that accepted this connection
+	 */
+	private NGServer server = null;
+
+	/**
+	 * The command that was issued for this connection
+	 */
+	private String command = null;
+
+	private String workingDirectory = null;
+	
+	/**
+	 * The client's stdin
+	 */
+	public InputStream in = null;
+
+	/**
+	 * The client's stdout
+	 */
+	public PrintStream out = null;
+
+	/**
+	 * The client's stderr
+	 */
+	public PrintStream err = null;
+
+	
+	/**
+	 * Creates a new, empty NGContext
+	 */
+	NGContext() {
+		super();
+	}
+	
+	void setExitStream(PrintStream exitStream) {
+		this.exitStream = exitStream;
+	}
+
+	void setPort(int remotePort) {
+		this.remotePort = remotePort;
+	}
+
+	void setCommand(String command) {
+		this.command = command;
+	}
+
+	/**
+	 * Returns the command that was issued by the client (either an alias or the name of a class).
+	 * This allows multiple aliases to point to the same class but result in different behaviors.
+	 * @return the command issued by the client
+	 */
+	public String getCommand() {
+		return (command);
+	}
+	
+	void setWorkingDirectory(String workingDirectory) {
+		this.workingDirectory = workingDirectory;
+	}
+	
+	/**
+	 * Returns the current working directory of the client, as reported by the client.
+	 * This is a String that will use the client's <code>File.separator</code> ('/' or '\'),
+	 * which may differ from the separator on the server. 
+	 * @return the current working directory of the client
+	 */
+	public String getWorkingDirectory() {
+		return (workingDirectory);
+	}
+	
+	void setEnv(Properties remoteEnvironment) {
+		this.remoteEnvironment = remoteEnvironment;
+	}
+
+	void setInetAddress(InetAddress remoteHost) {
+		this.remoteHost = remoteHost;
+	}
+
+	void setArgs(String[] args) {
+		this.args = args;
+	}
+
+	void setNGServer(NGServer server) {
+		this.server = server;
+	}
+
+	/**
+	 * Returns a <code>java.util.Properties</code> object containing a copy
+	 * of the client's environment variables
+	 * @see java.util.Properties
+	 * @return a <code>java.util.Properties</code> object containing a copy
+	 * of the client's environment variables
+	 */
+	public Properties getEnv() {
+		return (remoteEnvironment);
+	}
+
+	/**
+	 * Returns the file separator ('/' or '\\') used by the client's os.
+	 * @return the file separator ('/' or '\\') used by the client's os.
+	 */
+	public String getFileSeparator() {
+		return (remoteEnvironment.getProperty("NAILGUN_FILESEPARATOR"));
+	}
+	
+	/**
+	 * Returns the path separator (':' or ';') used by the client's os.
+	 * @return the path separator (':' or ';') used by the client's os.
+	 */
+	public String getPathSeparator() {
+		return (remoteEnvironment.getProperty("NAILGUN_PATHSEPARATOR"));		
+	}
+	
+	/**
+	 * Returns the address of the client at the other side of this connection.
+	 * @return the address of the client at the other side of this connection.
+	 */
+	public InetAddress getInetAddress() {
+		return (remoteHost);
+	}
+
+	/**
+	 * Returns the command line arguments for the command
+	 * implementation (nail) on the server.
+	 * @return the command line arguments for the command
+	 * implementation (nail) on the server.
+	 */
+	public String[] getArgs() {
+		return (args);
+	}
+
+	/**
+	 * Returns the NGServer that accepted this connection
+	 * @return the NGServer that accepted this connection
+	 */
+	public NGServer getNGServer() {
+		return (server);
+	}
+
+	/**
+	 * Sends an exit command with the specified exit code to
+	 * the client.  The client will exit immediately with
+	 * the specified exit code; you probably want to return
+	 * from nailMain immediately after calling this.
+	 * 
+	 * @param exitCode the exit code with which the client
+	 * should exit
+	 */
+	public void exit(int exitCode) {
+		exitStream.println(exitCode);
+	}
+
+	/**
+	 * Returns the port on the client connected to the NailGun
+	 * server.
+	 * @return the port on the client connected to the NailGun
+	 * server.
+	 */
+	public int getPort() {
+		return (remotePort);
+	}
+	
+	/**
+	 * Throws a <code>java.lang.SecurityException</code> if the client is not
+	 * connected via the loopback address.
+	 */
+	public void assertLoopbackClient() {
+		if (!getInetAddress().isLoopbackAddress()) {
+			throw (new SecurityException("Client is not at loopback address."));
+		}
+	}
+	
+	/**
+	 * Throws a <code>java.lang.SecurityException</code> if the client is not
+	 * connected from the local machine.
+	 */
+	public void assertLocalClient() {
+		NetworkInterface iface = null;
+		try {
+			iface = NetworkInterface.getByInetAddress(getInetAddress());
+		} catch (java.net.SocketException se) {
+			throw (new SecurityException("Unable to determine if client is local.  Assuming he isn't."));
+		}
+		
+		if ((iface == null) && (!getInetAddress().isLoopbackAddress())) {
+			throw (new SecurityException("Client is not local."));
+		}
+	}
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGExitException.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+import  java.io.PrintStream;
+
+import  org.apache.tools.ant.ExitException;
+
+/**
+ * Security exception which wraps an exit status code.
+ * @author Pete Kirkham
+ */
+public class NGExitException extends ExitException {
+        public NGExitException(int status) {
+                super(status);
+        }
+  
+        /**
+         * A lot of code out there, for example ant's Launcher,
+         * runs inside a try/catch (Throwable) which will squash
+         * this exception; most also calll printStackTrace(), so
+         * this re-throws the exception to escape the handling code.
+         */
+        public void printStackTrace (PrintStream out) {
+                throw this;
+        }
+        
+        public void reallyPrintStackTrace (PrintStream out) {
+                super.printStackTrace(out);
+        }
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGInputStream.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+
+/**
+ * A FilterInputStream that is able to read the chunked stdin stream
+ * from a NailGun client.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+class NGInputStream extends FilterInputStream {
+
+	private byte[] header;
+	private boolean eof = false;
+	private long remaining = 0;
+	
+	/**
+	 * Creates a new NGInputStream wrapping the specified InputStream
+	 * @param in the InputStream to wrap
+	 */
+	public NGInputStream(java.io.InputStream in) {
+		super(in);
+		header = new byte[5];
+	}
+
+	/**
+	 * Reads a NailGun chunk header from the underlying InputStream.
+	 * 
+	 * @throws IOException if thrown by the underlying InputStream,
+	 * or if an unexpected NailGun chunk type is encountered.
+	 */
+	private void readHeader() throws IOException {
+		if (eof) return;
+		
+		int bytesRead = in.read(header);
+		int thisPass = 0;
+		while (bytesRead < 5) {
+			thisPass = in.read(header, bytesRead, 5 - bytesRead);
+			if (thisPass < 0) {
+				eof = true;
+				return;
+			}
+			bytesRead += thisPass;
+		}
+		switch(header[4]) {
+			case NGConstants.CHUNKTYPE_STDIN:
+						remaining = LongUtils.fromArray(header, 0);
+						break;
+						
+			case NGConstants.CHUNKTYPE_STDIN_EOF:
+						eof = true;
+						break;
+						
+			default:	throw(new IOException("Unknown stream type: " + (char) header[4]));
+		}		
+	}
+	
+	/**
+	 * @see java.io.InputStream#available()
+	 */
+	public int available() throws IOException {
+		if (eof) return(0);
+		if (remaining > 0) return (in.available());
+		return (Math.max(0, in.available() - 5));
+	}
+	
+	/**
+	 * @see java.io.InputStream#markSupported()
+	 */
+	public boolean markSupported() {
+		return (false);
+	}
+	
+	/**
+	 * @see java.io.InputStream#read()
+	 */
+	public int read() throws IOException {
+		// this should be more readable.
+		// this stomps on the first byte of the header array,
+		// which is ok
+		return((read(header, 0, 1) == -1) ? -1 : (int) header[0]);
+	}
+	
+	/**
+	 * @see java.io.InputStream.read(byte[])
+	 */
+	public int read(byte[] b) throws IOException {
+		return (read(b, 0, b.length));
+	}
+	
+	/**
+	 * @see java.io.InputStream.read(byte[],offset,length)
+	 */
+	public int read(byte[] b, int offset, int length) throws IOException {
+		if (remaining == 0) readHeader();
+		if (eof) return(-1);
+
+		int bytesToRead = Math.min((int) remaining, length);
+		int result = in.read(b, offset, bytesToRead);
+		remaining -= result;
+		return (result);
+	}
+
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGOutputStream.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+import java.io.IOException;
+
+/**
+ * Wraps an OutputStream to send writes in NailGun chunks.  Because
+ * multiple NGOutputStreams wrap the same OutputStream (that is, 
+ * the OutputStream obtained from the Socket connection with
+ * the client), writes are synchronized on the underlying OutputStream.
+ * If this were not the case, write interleaving could completely
+ * break the NailGun protocol.
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+class NGOutputStream extends java.io.FilterOutputStream {
+
+	private byte[] header;
+	
+	/**
+	 * Creates a new NGOutputStream wrapping the specified
+	 * OutputStream and using the specified Nailgun chunk code.
+	 * @param out the OutputStream to wrap
+	 * @param code the NailGun chunk code associated with this
+	 * stream (i.e., '1' for stdout, '2' for stderr).
+	 */
+	public NGOutputStream(java.io.OutputStream out, char code) {
+		super(out);
+		header = new byte[5];
+		header[4] = (byte) code;
+	}
+	
+	/**
+	 * @see java.io.OutputStream.write(byte[])
+	 */
+	public void write(byte[] b) throws IOException {
+		write(b, 0, b.length);
+	}
+	
+	/**
+	 * @see java.io.OutputStream.write(int)
+	 */
+	public void write(int b) throws IOException {
+		byte[] b2 = {(byte) b};
+		write(b2, 0, 1);
+	}
+	
+	/**
+	 * @see java.io.OutputStream.write(byte[],int,int)
+	 */
+	public void write(byte[] b, int offset, int len) throws IOException {
+		LongUtils.toArray(len, header, 0);
+		synchronized(out) {
+			out.write(header, 0, 5);
+			out.write(b, offset, len);
+		}
+		out.flush();
+	}
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGSecurityManager.java

+/*   
+
+Copyright 2004, Martian Software, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+
+import  java.security.Permission;
+
+import  java.io.PrintStream;
+
+/**
+ * Security manager which does nothing other than trap
+ * checkExit, or delegate all non-deprecated methods to
+ * a base manager.
+ * 
+ * @author Pete Kirkham
+ * 
+ */
+public class NGSecurityManager extends SecurityManager {
+        private static final ThreadLocal EXIT = new InheritableThreadLocal();
+        final SecurityManager base;
+        
+        /**
+         * Construct an NGSecurityManager with the given base.
+         * @param base the base security manager, or null for no base.
+         */
+        public NGSecurityManager (SecurityManager base) {
+                this.base = base;
+        }
+
+        public void checkExit (int status) {
+                if (base != null) {
+                        base.checkExit(status);
+                }
+                
+                final PrintStream exit = (PrintStream)EXIT.get();
+                
+                if (exit != null) {
+                        exit.println(status);
+                }
+                
+                throw new NGExitException(status);
+        }
+
+        public void checkPermission(Permission perm) {
+                if (base != null) {
+                        base.checkPermission(perm);
+                }
+        }
+   
+        public void checkPermission(Permission perm, Object context) {
+                if (base != null) {
+                        base.checkPermission(perm, context);
+                }
+        }
+        
+        public static void setExit (PrintStream exit) {
+                EXIT.set(exit);
+        }
+}

nailgun-connector/src/main/java/vimclojure/nailgun/NGServer.java

+/*   
+
+  Copyright 2004, Martian Software, Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+*/
+
+package vimclojure.nailgun;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.Map;
+
+
+import clojure.lang.RT;
+import clojure.lang.Symbol;
+import clojure.lang.Var;
+
+import vimclojure.nailgun.builtins.DefaultNail;
+
+/**
+ * <p>Listens for new connections from NailGun clients and launches
+ * NGSession threads to process them.</p>
+ * 
+ * <p>This class can be run as a standalone server or can be embedded
+ * within larger applications as a means of providing command-line
+ * interaction with the application.</p>
+ * 
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ */
+public class NGServer implements Runnable {
+
+	/* Load up vimclojure */
+	static {
+		try {
+			RT.var("clojure.core", "require").invoke(
+				Symbol.create("vimclojure.core")
+			);
+			RT.var("vimclojure.core", "init-server").invoke();
+		} catch (Exception exc) {
+			Throwable e = exc;
+			System.err.println("A crisis has arisen:");
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * The address on which to listen, or null to listen on all
+	 * local addresses
+	 */
+	private InetAddress addr = null;
+	
+	/**
+	 * The port on which to listen, or zero to select a port automatically
+	 */
+	private int port = 0;
+	
+	/**
+	 * The socket doing the listening
+	 */
+	private ServerSocket serversocket;
+	
+	/**
+	 * True if this NGServer has received instructions to shut down
+	 */
+	private boolean shutdown = false;
+	
+	/**
+	 * True if this NGServer has been started and is accepting connections
+	 */
+	private boolean running = false;
+	
+	/**
+	 * This NGServer's AliasManager, which maps aliases to classes
+	 */
+	private AliasManager aliasManager;
+	
+	/**
+	 * If true, fully-qualified classnames are valid commands
+	 */
+	private boolean allowNailsByClassName = true;
+	
+	/**
+	 * The default class to use if an invalid alias or classname is
+	 * specified by the client.
+	 */
+	private Class defaultNailClass = null;
+	
+	/**
+	 * A pool of NGSessions ready to handle client connections
+	 */
+	private NGSessionPool sessionPool = null;
+	
+	/**
+	 * <code>System.out</code> at the time of the NGServer's creation
+	 */
+	public final PrintStream out = System.out;
+
+	/**
+	 * <code>System.err</code> at the time of the NGServer's creation
+	 */
+	public final PrintStream err = System.err;
+	
+	/**
+	 * <code>System.in</code> at the time of the NGServer's creation
+	 */
+	public final InputStream in = System.in;
+	
+	/**
+	 * a collection of all classes executed by this server so far
+	 */
+	private Map allNailStats = null;
+	
+	/**
+	 * Remember the security manager we start with so we can restore it later
+	 */
+	private SecurityManager originalSecurityManager = null;
+	
+	/**
+	 * Creates a new NGServer that will listen at the specified address and
+	 * on the specified port.
+	 * This does <b>not</b> cause the server to start listening.  To do
+	 * so, create a new <code>Thread</code> wrapping this <code>NGServer</code>
+	 * and start it.
+	 * @param addr the address at which to listen, or <code>null</code> to bind
+	 * to all local addresses
+	 * @param port the port on which to listen.
+	 */
+	public NGServer(InetAddress addr, int port) {
+		init(addr, port);
+	}
+	
+	/**
+	 * Creates a new NGServer that will listen on the default port
+	 * (defined in <code>NGConstants.DEFAULT_PORT</code>).
+	 * This does <b>not</b> cause the server to start listening.  To do
+	 * so, create a new <code>Thread</code> wrapping this <code>NGServer</code>
+	 * and start it.
+	 */
+	public NGServer() {
+		init(null, NGConstants.DEFAULT_PORT);
+	}
+	
+	/**
+	 * Sets up the NGServer internals
+	 * @param addr the InetAddress to bind to
+	 * @param port the port on which to listen
+	 */
+	private void init(InetAddress addr, int port) {
+		this.addr = addr;
+		this.port = port;
+		
+		this.aliasManager = new AliasManager();
+		allNailStats = new java.util.HashMap();
+		// allow a maximum of 10 idle threads.  probably too high a number
+		// and definitely should be configurable in the future
+		sessionPool = new NGSessionPool(this, 10);
+	}
+
+	/**
+	 * Sets a flag that determines whether Nails can be executed by class name.
+	 * If this is false, Nails can only be run via aliases (and you should
+	 * probably remove ng-alias from the AliasManager).
+	 * 
+	 * @param allowNailsByClassName true iff Nail lookups by classname are allowed
+	 */
+	public void setAllowNailsByClassName(boolean allowNailsByClassName) {
+		this.allowNailsByClassName = allowNailsByClassName;
+	}
+	
+	/**
+	 * Returns a flag that indicates whether Nail lookups by classname
+	 * are allowed.  If this is false, Nails can only be run via aliases.
+	 * @return a flag that indicates whether Nail lookups by classname
+	 * are allowed.
+	 */
+	public boolean allowsNailsByClassName() {
+		return (allowNailsByClassName);
+	}
+	
+	/**
+	 * Sets the default class to use for the Nail if no Nails can
+	 * be found via alias or classname. (may be <code>null</code>,
+	 * in which case NailGun will use its own default)
+	 * @param defaultNailClass the default class to use for the Nail
+	 * if no Nails can be found via alias or classname.
+	 * (may be <code>null</code>, in which case NailGun will use
+	 * its own default)
+	 */
+	public void setDefaultNailClass(Class defaultNailClass) {
+		this.defaultNailClass = defaultNailClass;
+	}
+	
+	/**
+	 * Returns the default class that will be used if no Nails
+	 * can be found via alias or classname.
+	 * @return the default class that will be used if no Nails
+	 * can be found via alias or classname.
+	 */
+	public Class getDefaultNailClass() {
+		return ((defaultNailClass == null) ? DefaultNail.class : defaultNailClass) ;
+	}
+	
+	/**
+	 * Returns the current NailStats object for the specified class, creating
+	 * a new one if necessary
+	 * @param nailClass the class for which we're gathering stats
+	 * @return a NailStats object for the specified class
+	 */
+	private NailStats getOrCreateStatsFor(Class nailClass) {
+		NailStats result = null;
+		synchronized(allNailStats) {
+			result = (NailStats) allNailStats.get(nailClass);
+			if (result == null) {
+				result = new NailStats(nailClass);
+				allNailStats.put(nailClass, result);
+			}
+		}
+		return (result);
+	}
+	
+	/**
+	 * Provides a means for an NGSession to register the starting of
+	 * a nail execution with the server.
+	 * 
+	 * @param nailClass the nail class that was launched
+	 */
+	void nailStarted(Class nailClass) {
+		NailStats stats = getOrCreateStatsFor(nailClass);
+		stats.nailStarted();
+	}
+	
+	/**
+	 * Provides a means for an NGSession to register the completion of
+	 * a nails execution with the server.
+	 * 
+	 * @param nailClass the nail class that finished
+	 */
+	void nailFinished(Class nailClass) {
+		NailStats stats = (NailStats) allNailStats.get(nailClass);
+		stats.nailFinished();
+	}
+	
+	/**
+	 * Returns a snapshot of this NGServer's nail statistics.  The result is a <code>java.util.Map</code>,
+	 * keyed by class name, with <a href="NailStats.html">NailStats</a> objects as values.
+	 * 
+	 * @return a snapshot of this NGServer's nail statistics.
+	 */
+	public Map getNailStats() {
+		Map result = new java.util.TreeMap();
+		synchronized(allNailStats) {
+			for (Iterator i = allNailStats.keySet().iterator(); i.hasNext();) {
+				Class nailclass = (Class) i.next();
+				result.put(nailclass.getName(), ((NailStats) allNailStats.get(nailclass)).clone());
+			}
+		}
+		return (result);
+	}
+	
+	/**
+	 * Returns the AliasManager in use by this NGServer.
+	 * @return the AliasManager in use by this NGServer.
+	 */
+	public AliasManager getAliasManager() {
+		return (aliasManager);
+	}
+
+	/**
+	 * <p>Shuts down the server.  The server will stop listening
+	 * and its thread will finish.  Any running nails will be allowed
+	 * to finish.</p>
+	 * 
+	 * <p>Any nails that provide a
+	 * <pre><code>public static void nailShutdown(NGServer)</code></pre>
+	 * method will have this method called with this NGServer as its sole
+	 * parameter.</p>
+	 * 
+	 * @param exitVM if true, this method will also exit the JVM after
+	 * calling nailShutdown() on any nails.  This may prevent currently
+	 * running nails from exiting gracefully, but may be necessary in order
+	 * to perform some tasks, such as shutting down any AWT or Swing threads
+	 * implicitly launched by your nails.
+	 */
+	public void shutdown(boolean exitVM) {
+		synchronized(this) {
+			if (shutdown) return;
+			shutdown = true;
+		}
+		
+		try {
+			serversocket.close();
+		} catch (Throwable toDiscard) {}
+		
+		sessionPool.shutdown();
+		
+		Class[] argTypes = new Class[1];
+		argTypes[0] = NGServer.class;
+		Object[] argValues = new Object[1];
+		argValues[0] = this;
+		
+		// make sure that all aliased classes have associated nailstats
+		// so they can be shut down.
+		for (Iterator i = getAliasManager().getAliases().iterator(); i.hasNext();) {
+			Alias alias = (Alias) i.next();
+			getOrCreateStatsFor(alias.getAliasedClass());
+		}
+		
+		synchronized(allNailStats) {
+			for (Iterator i = allNailStats.values().iterator(); i.hasNext();) {
+				NailStats ns = (NailStats) i.next();
+				Class nailClass = ns.getNailClass();
+				
+				// yes, I know this is lazy, relying upon the exception
+				// to handle the case of no nailShutdown method.
+				try {
+					Method nailShutdown = nailClass.getMethod("nailShutdown", argTypes);
+					nailShutdown.invoke(null, argValues);
+				} catch (Throwable toDiscard) {}
+			}
+		}
+		
+		// restore system streams
+		System.setIn(in);
+		System.setOut(out);
+		System.setErr(err);
+		
+		System.setSecurityManager(originalSecurityManager);
+		
+		if (exitVM) {
+			System.exit(0);
+		}
+	}
+	
+	/**
+	 * Returns true iff the server is currently running.
+	 * @return true iff the server is currently running.
+	 */
+	public boolean isRunning() {
+		return (running);
+	}
+	
+	/**
+	 * Returns the port on which this server is (or will be) listening.
+	 * @return the port on which this server is (or will be) listening.
+	 */
+	public int getPort() {
+		return ((serversocket == null) ? port : serversocket.getLocalPort());
+	}
+	
+	/**
+	 * Listens for new connections and launches NGSession threads
+	 * to process them.
+	 */
+	public void run() {
+		running = true;
+		NGSession sessionOnDeck = null;
+		
+		originalSecurityManager = System.getSecurityManager();
+        System.setSecurityManager(
+                new NGSecurityManager(
+                        originalSecurityManager));
+  
+
+		synchronized(System.in) {
+			if (!(System.in instanceof ThreadLocalInputStream)) {
+				System.setIn(new ThreadLocalInputStream(in));
+				System.setOut(new ThreadLocalPrintStream(out));
+				System.setErr(new ThreadLocalPrintStream(err));				
+			}
+		}
+		
+		try {
+			if (addr == null) {
+				serversocket = new ServerSocket(port);
+			} else {
+				serversocket = new ServerSocket(port, 0, addr);
+			}
+			
+			while (!shutdown) {
+				sessionOnDeck = sessionPool.take();
+				Socket socket = serversocket.accept();
+				/* FIXME: Why is this necessary? */
+				Thread.sleep(50);
+				sessionOnDeck.run(socket);
+			}
+
+		} catch (Throwable t) {
+			// if shutdown is called while the accept() method is blocking,
+			// an exception will be thrown that we don't care about.  filter
+			// those out.
+			if (!shutdown) {
+				t.printStackTrace();
+			}
+		}
+		if (sessionOnDeck != null) {
+			sessionOnDeck.shutdown();
+		}
+		running = false;
+	}
+	
+	private static void usage() {
+		System.err.println("Usage: java vimclojure.nailgun.NGServer");
+		System.err.println("   or: java vimclojure.nailgun.NGServer port");
+		System.err.println("   or: java vimclojure.nailgun.NGServer IPAddress");
+		System.err.println("   or: java vimclojure.nailgun.NGServer IPAddress:port");
+	}
+	
+	/**
+	 * Creates and starts a new <code>NGServer</code>.  A single optional
+	 * argument is valid, specifying the port on which this <code>NGServer</code>
+	 * should listen.  If omitted, <code>NGServer.DEFAULT_PORT</code> will be used.
+	 * @param args a single optional argument specifying the port on which to listen.
+	 * @throws NumberFormatException if a non-numeric port is specified
+	 */
+	public static void main(String[] args) throws NumberFormatException, UnknownHostException {
+
+		if (args.length > 1) {
+			usage();
+			return;
+		}
+
+		// null server address means bind to everything local
+		InetAddress serverAddress = null;
+		int port = NGConstants.DEFAULT_PORT;
+		
+		// parse the sole command line parameter, which
+		// may be an inetaddress to bind to, a port number,
+		// or an inetaddress followed by a port, separated
+		// by a colon
+		if (args.length != 0) {
+			String[] argParts = args[0].split(":");
+			String addrPart = null;
+			String portPart = null;
+			if (argParts.length == 2) {
+				addrPart = argParts[0];
+				portPart = argParts[1];
+			} else if (argParts[0].indexOf('.') >= 0) {
+				addrPart = argParts[0];
+			} else {
+				portPart = argParts[0];
+			}
+			if (addrPart != null) {
+				serverAddress = InetAddress.getByName(addrPart);
+			}
+			if (portPart != null) {
+				port = Integer.parseInt(portPart);
+			}
+		}
+
+		try {
+			Class.forName("clojure.lang.IFn");
+		} catch (ClassNotFoundException ignore) {
+			System.err.println("ERROR: Could not find clojure.lang.IFn on the classpath!");
+			System.err.println("ERROR: This most likely means that Clojure is not on the classpath!");
+			System.err.println("ERROR: Please check your settings!");
+			System.err.println("ERROR: Here is the classpath for your reference:");
+			System.err.println(System.getProperty("java.class.path"));
+			return;
+		}