Commits

Meikel Brandmeyer committed 9336a08

Check-in nailgun sources

Comments (0)

Files changed (28)

+# This Makefile has only been tested on linux.  It uses
+# MinGW32 to cross-compile for windows.  To install and
+# configure MinGW32 on linux, see
+# http://www.mingw.org/MinGWiki/index.php/BuildMingwCross
+#
+# Marty Lamb
+
+CC=gcc
+WIN32_CC=/usr/local/mingw32/bin/mingw32-gcc
+CFLAGS=-Wall -pedantic -s -O3
+
+ng: ngclient/ng.c
+	@echo "Building ng client.  To build a Windows binary, type 'make ng.exe'"
+	${CC} ${CFLAGS} -o ng ngclient/ng.c
+
+ng.exe: ngclient/ng.c
+	# any idea why the command line is so sensitive to the order of
+	# the arguments?  If CFLAGS is at the beginning, it won't link.
+	${WIN32_CC} -o ng.exe ngclient/ng.c -lwsock32 -O3 ${CFLAGS}
+	
+clean:
+	@echo "If you have a Windows binary, 'make clean' won't delete it."
+	@echo "You must remove this manually.  Most users won't have MinGW"
+	@echo "installed - so I'd rather not delete something they can't rebuild."
+	rm ng
+#	rm ng.exe
-<project name="gorilla" default="jar">
+<project name="gorilla" default="all">
 
 	<description>
-		Build with "ant jar".
+		Build with "ant all".
 	</description>
 
 	<property environment="env"/>
 		<mkdir dir="${build}"/>
 	</target>
 
-	<target name="aot" depends="init"
+	<target name="aot" depends="init,nailgun-server"
 		description="Compile Clojure sources.">
 		<java classname="clojure.lang.Compile">
 			<classpath>
 		</java>
 	</target>
 
-	<target name="prepare-full-jar" depends="init,aot"
+	<target name="nailgun-server" depends="init"
+		description="Compile the nailgun server.">
+		<javac destdir="${build}" srcdir="${src}"
+			includes="org/apache/**/*.java">
+		</javac>
+		<javac destdir="${build}" srcdir="${src}"
+			includes="com/martiansoftware/**/*.java">
+		</javac>
+		<copy todir="${build}">
+			<fileset dir="${src}" includes="com/martiansoftware/**"
+				excludes="com/martiansoftware/**/*.java"/>
+		</copy>
+	</target>
+
+	<target name="nailgun-client"
+		description="Compile the nailgun client using make.">
+		<exec executable="make">
+			<arg value="${nailgun-client}"/>
+		</exec>
+	</target>
+
+	<target name="prepare-full-jar" depends="init,aot,nailgun-server"
 		description="Include clojure and contrib sources."
 		if="standalone">
 		<unzip dest="${build}">
 		</jar>
 	</target>
 
-	<target name="prepare-small-jar" depends="aot"
+	<target name="prepare-small-jar" depends="aot,nailgun-server"
 			description="Create jar file."
 			unless="standalone">
 		<jar jarfile="${gorilla_jar}">
 			<path location="README.txt"/>
 			<path location="LICENSE.txt"/>
 			<fileset dir="${src}" includes="**/*.clj"/>
-			<fileset dir="${build}" includes="de/kotka/**/*.class"/>
+			<fileset dir="${build}" includes="de/kotka/**"/>
+			<fileset dir="${build}" includes="org/apache/**"/>
+			<fileset dir="${build}" includes="com/martiansoftware/**"/>
 			<manifest>
 				<attribute name="Class-Path" value="."/>
 			</manifest>
 
 	<target name="jar" depends="prepare-small-jar,prepare-full-jar"/>
 
+	<target name="all" depends="jar,nailgun-client"/>
+
 	<target name="clean"
 		description="Remove autogenerated files and directories.">
 		<delete dir="${build}"/>
 		<delete file="${gorilla_jar}"/>
+		<delete file="${nailgun-client}"/>
 	</target>
 
 </project>
+<!-- vim: set ts=4 sw=4 : -->
Binary file added.
+/*   
+
+  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.
+  
+*/
+
+/**
+ * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
+ * @author Pete Kirkham (Win32 port)
+ */
+
+#ifdef WIN32
+	#include <direct.h>
+	#include <winsock2.h>
+#else
+	#include <arpa/inet.h>
+	#include <netdb.h>
+	#include <netinet/in.h>
+	#include <sys/socket.h>
+	#include <sys/types.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define NAILGUN_VERSION "0.7.1"
+
+#define BUFSIZE (2048)
+
+#ifdef WIN32
+	HANDLE NG_STDIN_FILENO;
+	HANDLE NG_STDOUT_FILENO;
+	HANDLE NG_STDERR_FILENO;
+	#define FILE_SEPARATOR '\\'
+	#define MSG_WAITALL 0
+#else
+	#define NG_STDIN_FILENO STDIN_FILENO
+	#define NG_STDOUT_FILENO STDOUT_FILENO
+	#define NG_STDERR_FILENO STDERR_FILENO
+	#define FILE_SEPARATOR '/'
+	typedef int HANDLE;
+	typedef unsigned int SOCKET;
+	/* buffer used for reading an writing chunk data */
+	char buf[BUFSIZE];
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a<b)?(a):(b))
+#endif
+
+#ifdef WIN32
+	#define NAILGUN_FILESEPARATOR "NAILGUN_FILESEPARATOR=\\"
+	#define NAILGUN_PATHSEPARATOR "NAILGUN_PATHSEPARATOR=;"
+#else
+	#define NAILGUN_FILESEPARATOR "NAILGUN_FILESEPARATOR=/"
+	#define NAILGUN_PATHSEPARATOR "NAILGUN_PATHSEPARATOR=:"
+#endif
+
+#define NAILGUN_CLIENT_NAME_EXE "ng.exe"
+
+#define NAILGUN_PORT_DEFAULT "2113"
+#define NAILGUN_CLIENT_NAME "ng"
+#define CHUNK_HEADER_LEN (5)
+
+#define NAILGUN_SOCKET_FAILED (999)
+#define NAILGUN_CONNECT_FAILED (998)
+#define NAILGUN_UNEXPECTED_CHUNKTYPE (997)
+#define NAILGUN_EXCEPTION_ON_SERVER (996)
+#define NAILGUN_CONNECTION_BROKEN (995)
+#define NAILGUN_BAD_ARGUMENTS (994)
+
+#define CHUNKTYPE_STDIN '0'
+#define CHUNKTYPE_STDOUT '1'
+#define CHUNKTYPE_STDERR '2'
+#define CHUNKTYPE_STDIN_EOF '.'
+#define CHUNKTYPE_ARG 'A'
+#define CHUNKTYPE_ENV 'E'
+#define CHUNKTYPE_DIR 'D'
+#define CHUNKTYPE_CMD 'C'
+#define CHUNKTYPE_EXIT 'X'
+
+
+/* the socket connected to the nailgun server */
+int nailgunsocket = 0;
+
+/**
+ * Clean up the application.
+ */
+void cleanUpAndExit (int exitCode) {
+
+
+  #ifdef WIN32
+    CancelIo(STDIN_FILENO);
+    WSACleanup();
+    if (nailgunsocket) {
+      closesocket(nailgunsocket);
+    }
+  #else
+    close(nailgunsocket);
+  #endif
+
+  exit(exitCode);
+}
+
+#ifdef WIN32
+/**
+ * Handles an error.
+ * Shows the message for the latest error then exits.
+ */
+void handleError () {
+  LPVOID lpMsgBuf;
+  int error = GetLastError();
+
+  FormatMessage( 
+    FORMAT_MESSAGE_ALLOCATE_BUFFER | 
+    FORMAT_MESSAGE_FROM_SYSTEM | 
+    FORMAT_MESSAGE_IGNORE_INSERTS,
+    NULL,
+    error,
+    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
+    (LPTSTR) &lpMsgBuf,
+    0,
+    NULL);
+
+  /* Display the string. */
+  MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONERROR );
+
+  /* Free the buffer. */
+  LocalFree( lpMsgBuf );
+ 
+  cleanUpAndExit(error);
+}
+#endif
+
+/**
+ * Writes everything in the specified buffer to the specified
+ * socket handle.
+ *
+ * @param s the socket descriptor
+ * @param buf the buffer containing the data to send
+ * @param len the number of bytes to send.  Also used to
+ *            return the number of bytes sent.
+ * @return total bytes written or 0 if failure
+ */
+int sendAll(SOCKET s, char *buf, int len) {
+  int total = 0;      
+  int bytesleft = len; 
+  int n = 0;
+    
+  while(total < len) {
+    n = send(s, buf+total, bytesleft, 0);
+    
+    if (n == -1) { 
+      break;
+    }
+    
+    total += n;
+    bytesleft -= n;
+  }
+
+  return n==-1 ? 0:total; 
+}
+
+/**
+ * Sends a chunk header noting the specified payload size and chunk type.
+ * 
+ * @param size the payload size
+ * @param chunkType the chunk type identifier
+ */
+void sendHeader(unsigned int size, char chunkType) {
+  /* buffer used for reading and writing chunk headers */
+  char header[CHUNK_HEADER_LEN];
+
+  header[0] = (size >> 24) & 0xff;
+  header[1] = (size >> 16) & 0xff;
+  header[2] = (size >> 8) & 0xff;
+  header[3] = size & 0xff;
+  header[4] = chunkType;
+
+  sendAll(nailgunsocket, header, CHUNK_HEADER_LEN);
+}
+
+/**
+ * Sends a null-terminated string with the specified chunk type.
+ *
+ * @param chunkType the chunk type identifier
+ * @param text the null-terminated string to send
+ */
+void sendText(char chunkType, char *text) {
+  int len = strlen(text);
+  sendHeader(len, chunkType);
+  sendAll(nailgunsocket, text, len);
+}
+
+/**
+ * Exits the client if the nailgun server ungracefully shut down the connection.
+ */
+void handleSocketClose() {
+  cleanUpAndExit(NAILGUN_CONNECTION_BROKEN);
+}
+
+/**
+ * Receives len bytes from the nailgun socket and copies them to the specified file descriptor.
+ * Used to route data to stdout or stderr on the client.
+ *
+ * @param destFD the destination file descriptor (stdout or stderr)
+ * @param len the number of bytes to copy
+ */
+void recvToFD(HANDLE destFD, char *buf, unsigned long len) {
+  unsigned long bytesRead = 0;
+  int bytesCopied;
+  
+  while (bytesRead < len) {
+    unsigned long bytesRemaining = len - bytesRead;
+    int bytesToRead = (BUFSIZE < bytesRemaining) ? BUFSIZE : bytesRemaining;
+    int thisPass = 0;
+    
+    thisPass = recv(nailgunsocket, buf, bytesToRead, MSG_WAITALL);
+   
+    bytesRead += thisPass;
+
+    bytesCopied = 0;
+
+    while(bytesCopied < thisPass) {
+      #ifdef WIN32
+        DWORD thisWrite =  0;
+
+        WriteFile(destFD, buf + bytesCopied, thisPass - bytesCopied,
+          &thisWrite, NULL);
+
+        if (thisWrite < 0) {
+          break;
+        }
+
+        bytesCopied += thisWrite;
+      #else
+        bytesCopied += write(destFD, buf + bytesCopied, thisPass - bytesCopied);
+      #endif
+    }  
+  }
+}
+
+
+/**
+ * Processes an exit chunk from the server.  This is just a string
+ * containing the exit code in decimal format.  It should fit well
+ * within our buffer, so assume that it does.
+ *
+ * @param len the current length of the buffer containing the exit code.
+ */
+void processExit(char *buf, unsigned long len) {
+  int exitcode;
+  int bytesToRead = (BUFSIZE - 1 < len) ? BUFSIZE - 1 : len;
+  int bytesRead = recv(nailgunsocket, buf, bytesToRead, MSG_WAITALL);
+  
+  if (bytesRead < 0) {
+    handleSocketClose();
+  }
+  
+  buf[bytesRead] = 0;
+  
+  exitcode = atoi(buf);
+  
+  cleanUpAndExit(exitcode);
+}
+
+/**
+ * Processes data from the nailgun server.
+ */
+void processnailgunstream() {
+  /* buffer used for receiving and writing nail output chunks */
+  char buf[BUFSIZE];
+
+  /*for (;;) {*/
+    int bytesRead = 0;
+    unsigned long len;
+    char chunkType;
+
+    bytesRead = recv(nailgunsocket, buf, CHUNK_HEADER_LEN, 0);
+
+    if (bytesRead < CHUNK_HEADER_LEN) {
+      handleSocketClose();
+    }
+  
+    len = ((buf[0] << 24) & 0xff000000)
+      | ((buf[1] << 16) & 0x00ff0000)
+      | ((buf[2] << 8) & 0x0000ff00)
+      | ((buf[3]) & 0x000000ff);
+  
+    chunkType = buf[4];
+  
+    switch(chunkType) {
+      case CHUNKTYPE_STDOUT: recvToFD(NG_STDOUT_FILENO, buf, len);
+            break;
+      case CHUNKTYPE_STDERR: recvToFD(NG_STDERR_FILENO, buf, len);
+            break;
+      case CHUNKTYPE_EXIT:   processExit(buf, len);
+            break;
+      default:  fprintf(stderr, "Unexpected chunk type %d ('%c')\n", chunkType, chunkType);
+          cleanUpAndExit(NAILGUN_UNEXPECTED_CHUNKTYPE);
+    }
+  /*}*/
+}
+
+/**
+ * Sends len bytes from buf to the nailgun server in a stdin chunk.
+ *
+ * @param buf the bytes to send
+ * @param len the number of bytes to send
+ */
+void sendStdin(char *buf, unsigned int len) {
+  sendHeader(len, CHUNKTYPE_STDIN);
+  sendAll(nailgunsocket, buf, len);
+}
+
+/**
+ * Sends a stdin-eof chunk to the nailgun server
+ */
+void processEof() {
+  sendHeader(0, CHUNKTYPE_STDIN_EOF);
+}
+
+
+#ifdef WIN32
+/**
+ * Thread main for reading from stdin and sending
+ */
+DWORD WINAPI processStdin (LPVOID lpParameter) {
+  /* buffer used for reading and sending stdin chunks */
+  char buf[BUFSIZE];
+
+  for (;;) {
+    DWORD numberOfBytes = 0;
+
+    if (!ReadFile(NG_STDIN_FILENO, buf, BUFSIZE, &numberOfBytes, NULL)) {
+      if (numberOfBytes != 0) {
+        handleError();
+      }
+    }
+
+    if (numberOfBytes > 0) {
+      sendStdin(buf, numberOfBytes);
+    } else {
+      processEof();
+      break;
+    }
+  }
+
+  return 0;
+}
+#else
+/**
+ * Reads from stdin and transmits it to the nailgun server in a stdin chunk.
+ * Sends a stdin-eof chunk if necessary.
+ *
+ * @return zero if eof has been reached.
+ */
+int processStdin() {
+	int bytesread = read(STDIN_FILENO, buf, BUFSIZE);
+	if (bytesread > 0) {
+		sendStdin(buf, bytesread);
+	} else if (bytesread == 0) {
+		processEof();
+	}
+	return(bytesread);
+}
+#endif
+
+#ifdef WIN32
+/**
+ * Initialise Windows sockets
+ */
+void initSockets () {
+  WSADATA win_socket_data;     /* required to initialise winsock */
+  
+  WSAStartup(2, &win_socket_data);
+}
+#endif
+
+#ifdef WIN32
+/**
+ * Initialise the asynchronous io.
+ */
+void initIo () {
+  SECURITY_ATTRIBUTES securityAttributes;
+  DWORD threadId = 0;
+
+  /* create non-blocking console io */
+  AllocConsole();
+
+  securityAttributes.bInheritHandle = TRUE;
+  securityAttributes.lpSecurityDescriptor = NULL;
+  securityAttributes.nLength = 0;
+  
+  NG_STDIN_FILENO = GetStdHandle(STD_INPUT_HANDLE);
+  NG_STDOUT_FILENO = GetStdHandle(STD_OUTPUT_HANDLE);
+  NG_STDERR_FILENO = GetStdHandle(STD_ERROR_HANDLE);
+   
+  if (!CreateThread(&securityAttributes, 0, &processStdin, NULL, 0, &threadId)) {
+    handleError();
+  }
+}
+#endif
+
+/**
+ * Trims any path info from the beginning of argv[0] to determine
+ * the name used to launch the client.
+ *
+ * @param s argv[0]
+ */
+char *shortClientName(char *s) {
+  char *result = strrchr(s, FILE_SEPARATOR);
+  return ((result == NULL) ? s : result + 1);
+}
+
+/**
+ * Returns true if the specified string is the name of the nailgun
+ * client.  The comparison is made case-insensitively for windows.
+ *
+ * @param s the program name to check
+ */
+int isNailgunClientName(char *s) {
+  #ifdef WIN32
+  return (!strcasecmp(s, NAILGUN_CLIENT_NAME) ||
+           !strcasecmp(s, NAILGUN_CLIENT_NAME_EXE));
+  #else
+  return(!(strcmp(s, NAILGUN_CLIENT_NAME)));
+  #endif
+}
+
+/**
+ * Displays usage info and bails
+ */
+void usage(int exitcode) {
+
+  fprintf(stderr, "Usage: ng class [--nailgun-options] [args]\n");
+  fprintf(stderr, "          (to execute a class)\n");
+  fprintf(stderr, "   or: ng alias [--nailgun-options] [args]\n");
+  fprintf(stderr, "          (to execute an aliased class)\n");
+  fprintf(stderr, "   or: alias [--nailgun-options] [args]\n");
+  fprintf(stderr, "          (to execute an aliased class, where \"alias\"\n");
+  fprintf(stderr, "           is both the alias for the class and a symbolic\n");
+  fprintf(stderr, "           link to the ng client)\n\n");
+  
+  fprintf(stderr, "where options include:\n");
+  fprintf(stderr, "   --nailgun-D<name>=<value>   set/override a client environment variable\n");
+  fprintf(stderr, "   --nailgun-version           print product version and exit\n");
+  fprintf(stderr, "   --nailgun-showversion       print product version and continue\n");
+  fprintf(stderr, "   --nailgun-server            to specify the address of the nailgun server\n");
+  fprintf(stderr, "                               (default is localhost)\n");
+  fprintf(stderr, "   --nailgun-port              to specify the port of the nailgun server\n");
+  fprintf(stderr, "                               (default is 2113)\n");
+  fprintf(stderr, "   --nailgun-help              print this message and exit\n");
+
+  cleanUpAndExit(exitcode);
+}
+
+int main(int argc, char *argv[], char *env[]) {
+  int i;
+  struct sockaddr_in server_addr;
+  char *nailgun_server;        /* server as specified by user */
+  char *nailgun_port;          /* port as specified by user */
+  char *cwd;
+  u_short port;                /* port */
+  struct hostent *hostinfo;
+  char *cmd;
+  int firstArgIndex;           /* the first argument _to pass to the server_ */
+
+  #ifndef WIN32
+    fd_set readfds;
+    int eof = 0;
+  #endif
+
+  #ifdef WIN32
+  initSockets();
+  #endif
+  
+  /* start with environment variable.  default to localhost if not defined. */
+  nailgun_server = getenv("NAILGUN_SERVER");
+  if (nailgun_server == NULL) {
+    nailgun_server = "127.0.0.1";
+  }
+  
+  /* start with environment variable.  default to normal nailgun port if not defined */
+  nailgun_port = getenv("NAILGUN_PORT");
+  if (nailgun_port == NULL) {
+    nailgun_port = NAILGUN_PORT_DEFAULT;
+  }
+  
+  /* look at the command used to launch this program.  if it was "ng", then the actual
+     command to issue to the server must be specified as another argument.  if it
+     wasn't ng, assume that the desired command name was symlinked to ng in the user's
+     filesystem, and use the symlink name (without path info) as the command for the server. */
+  cmd = shortClientName(argv[0]);
+
+  if (isNailgunClientName(cmd)) {
+    cmd = NULL;
+  }
+  
+  firstArgIndex = 1;
+
+  /* quite possibly the lamest commandline parsing ever. 
+     look for the two args we care about (--nailgun-server and
+     --nailgun-port) and NULL them and their parameters after
+     reading them if found.  later, when we send args to the
+     server, skip the null args. */
+  for (i = 1; i < argc; ++i) {
+    if (!strcmp("--nailgun-server", argv[i])) {
+      if (i == argc - 1) usage(NAILGUN_BAD_ARGUMENTS);
+      nailgun_server = argv[i + 1];
+      argv[i] = argv[i + 1] = NULL;
+      ++i;
+    } else if(!strcmp("--nailgun-port", argv[i])) {
+      if (i == argc - 1) usage(NAILGUN_BAD_ARGUMENTS);
+      nailgun_port = argv[i + 1];
+      argv[i] = argv[i + 1]= NULL;
+      ++i;
+    } else if (!strcmp("--nailgun-version", argv[i])) {
+      printf("NailGun client version %s\n", NAILGUN_VERSION);
+      cleanUpAndExit(0);
+    } else if (!strcmp("--nailgun-showversion", argv[i])) {
+      printf("NailGun client version %s\n", NAILGUN_VERSION);
+      argv[i] = NULL;
+    } else if (!strcmp("--nailgun-help", argv[i])) {
+      usage(0);
+    } else if (cmd == NULL) {
+      cmd = argv[i];
+      firstArgIndex = i + 1;
+    }
+  }
+
+  /* if there's no command, we should only display usage info
+     if the version number was not displayed. */
+  if (cmd == NULL) {
+    usage(NAILGUN_BAD_ARGUMENTS);
+  }
+  
+  /* jump through a series of connection hoops */  
+  hostinfo = gethostbyname(nailgun_server);
+
+  if (hostinfo == NULL) {
+    fprintf(stderr, "Unknown host: %s\n", nailgun_server);
+    cleanUpAndExit(NAILGUN_CONNECT_FAILED);
+  }
+ 
+  port = atoi(nailgun_port);
+
+  if ((nailgunsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+    perror("socket");
+    cleanUpAndExit(NAILGUN_SOCKET_FAILED);
+  }
+
+  server_addr.sin_family = AF_INET;    
+  server_addr.sin_port = htons(port);
+  server_addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
+  
+  memset(&(server_addr.sin_zero), '\0', 8);
+
+  if (connect(nailgunsocket, (struct sockaddr *)&server_addr,
+    sizeof(struct sockaddr)) == -1) {
+    perror("connect");
+    cleanUpAndExit(NAILGUN_CONNECT_FAILED);
+  } 
+    
+  /* ok, now we're connected.  first send all of the command line
+     arguments for the server, if any.  remember that we may have
+     marked some arguments NULL if we read them to specify the
+     nailgun server and/or port */
+  for(i = firstArgIndex; i < argc; ++i) {
+    if (argv[i] != NULL) sendText(CHUNKTYPE_ARG, argv[i]);
+  }
+
+  /* now send environment */  
+  sendText(CHUNKTYPE_ENV, NAILGUN_FILESEPARATOR);
+  sendText(CHUNKTYPE_ENV, NAILGUN_PATHSEPARATOR);
+  for(i = 0; env[i]; ++i) {
+    sendText(CHUNKTYPE_ENV, env[i]);
+  }
+  
+  /* now send the working directory */
+  cwd = getcwd(NULL, 0);
+  sendText(CHUNKTYPE_DIR, cwd);
+  free(cwd);
+  
+  /* and finally send the command.  this marks the point at which
+     streams are linked between client and server. */
+  sendText(CHUNKTYPE_CMD, cmd);
+
+  
+  /* initialise the std-* handles and the thread to send stdin to the server */ 
+  #ifdef WIN32
+  initIo();
+  #endif
+
+  /* stream forwarding loop */	
+  while(1) {
+    #ifndef WIN32
+      FD_ZERO(&readfds);
+
+      /* don't select on stdin if we've already reached its end */
+      if (!eof) {
+	FD_SET(NG_STDIN_FILENO, &readfds);
+      }
+
+      FD_SET(nailgunsocket, &readfds);
+      if (select (nailgunsocket + 1, &readfds, NULL, NULL, NULL) == -1) {
+	perror("select");
+      }
+	  
+      if (FD_ISSET(nailgunsocket, &readfds)) {
+    #endif
+	processnailgunstream();
+    #ifndef WIN32
+      } else if (FD_ISSET(NG_STDIN_FILENO, &readfds)) {
+	if (!processStdin()) {
+	  FD_CLR(NG_STDIN_FILENO, &readfds);
+	  eof = 1;
+	}
+      }
+    #endif
+  }  
+
+  /* normal termination is triggered by the server, and so occurs in processExit(), above */
+}

src/com/martiansoftware/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 com.martiansoftware.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()));
+	}
+}

src/com/martiansoftware/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 com.martiansoftware.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("com/martiansoftware/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));
+	}
+
+}

src/com/martiansoftware/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 com.martiansoftware.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)));
+	}
+}

src/com/martiansoftware/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 com.martiansoftware.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("com/martiansoftware/nailgun/nailgun-version.properties"));
+		} catch (java.io.IOException e) {
+			System.err.println("Unable to load nailgun-version.properties.");
+		}
+		VERSION = props.getProperty("version", "UNKNOWN");
+	}
+	
+}

src/com/martiansoftware/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 com.martiansoftware.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."));
+		}
+	}
+}

src/com/martiansoftware/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 com.martiansoftware.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);
+        }
+}

src/com/martiansoftware/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 com.martiansoftware.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);
+	}
+
+}

src/com/martiansoftware/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 com.martiansoftware.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();
+	}
+}

src/com/martiansoftware/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 com.martiansoftware.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);
+        }
+}

src/com/martiansoftware/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 com.martiansoftware.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 com.martiansoftware.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 {
+
+	/**
+	 * 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());
+		}
+		
<