Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial checkin

  • Loading branch information...
commit 05aa391119fb4edc0f966d3571dfc9ef4b5f7b69 0 parents
@djpowell authored
6 .gitignore
@@ -0,0 +1,6 @@
+**/.classpath
+**/.project
+**/.settings
+**/*.jardesc
+**/.settings/**
+liverepl-server/lib/clojure.jar
40 README.TXT
@@ -0,0 +1,40 @@
+Clojure Live REPL - 2009-10-18
+David Powell <djpowell@djpowell.net>
+<http://github.com/djpowell/liverepl>
+
+----------------------------------------
+
+Build
+=====
+
+To build:
+
+ Copy clojure.jar to: ./liverepl-server/lib/clojure.jar
+
+ Run ant
+
+The build will be copied to: ./build/
+
+----------------------------------------
+
+Configuration
+=============
+
+Edit liverepl.bat to point to your installed JDK
+
+----------------------------------------
+
+Opearation
+==========
+
+To see a list of running Java processes on the system, and their
+process ids, enter:
+
+ liverepl
+
+To connect a repl shell to a running Java process, enter:
+
+ liverepl <pid>
+
+ -- where the pid is the process id for the process.
+
24 build.xml
@@ -0,0 +1,24 @@
+<project name="liverepl" basedir="." default="build">
+
+ <target name="clean" description="delete build and classes">
+ <ant dir="./liverepl-agent" target="clean" />
+ <ant dir="./liverepl-server" target="clean" />
+ <delete dir="./build" />
+ </target>
+
+ <target name="build" description="build">
+ <mkdir dir="./build" />
+ <ant dir="./liverepl-agent" />
+ <ant dir="./liverepl-server" />
+ <copy todir="./build">
+ <fileset dir="./liverepl-server/build" />
+ <fileset dir="./liverepl-server/lib" />
+ <fileset dir="./liverepl-agent/build" />
+ <fileset dir="./liverepl-agent/lib" />
+ <fileset file="./liverepl.bat" />
+ <fileset file="./README.TXT" />
+ </copy>
+ </target>
+
+</project>
+
29 liverepl-agent/build.xml
@@ -0,0 +1,29 @@
+<project name="liverepl-agent" basedir="." default="jar">
+
+ <target name="clean" description="delete build and classes">
+ <delete dir="./build" />
+ <delete dir="./classes" />
+ </target>
+
+ <target name="compile">
+ <mkdir dir="./classes" />
+ <javac debug="yes"
+ srcdir="./src"
+ destdir="./classes"
+ includes="**/*.java">
+ <classpath>
+ <fileset dir="./lib" includes="*.jar" />
+ </classpath>
+ </javac>
+ </target>
+
+ <target name="jar" description="build liverepl-agent.jar" depends="compile">
+ <mkdir dir="./build" />
+ <jar destfile="build/liverepl-agent.jar" manifest="src/META-INF/MANIFEST.MF">
+ <fileset dir="./classes" />
+ <fileset dir="./src" includes="**/*.clj" />
+ </jar>
+ </target>
+
+</project>
+
3  liverepl-agent/src/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Agent-Class: net.djpowell.liverepl.agent.Agent
+Main-Class: net.djpowell.liverepl.client.Main
103 liverepl-agent/src/net/djpowell/liverepl/agent/Agent.java
@@ -0,0 +1,103 @@
+package net.djpowell.liverepl.agent;
+
+import java.io.File;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+
+import net.djpowell.liverepl.client.Main;
+
+public class Agent {
+
+ private static ClassLoader pushClassLoader(List<URL> urls) {
+ TRC.fine("Creating new classloader with: " + urls);
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ TRC.fine("Old classloader: " + old);
+ URLClassLoader withClojure = new URLClassLoader(urls.toArray(new URL[urls.size()]), old); // TODO
+ Thread.currentThread().setContextClassLoader(withClojure);
+ return old;
+ }
+
+ private static void popClassLoader(ClassLoader old) {
+ TRC.fine("Restoring old context classloader");
+ Thread.currentThread().setContextClassLoader(old);
+ }
+
+ private static boolean isClojureLoaded() {
+ try {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ cl.loadClass("clojure.lang.RT");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ public static void agentmain(String agentArgs, Instrumentation inst) {
+ TRC.fine("Started Attach agent");
+
+ StringTokenizer stok = new StringTokenizer(agentArgs, "\n");
+ if (stok.countTokens() != 3) {
+ throw new RuntimeException("Invalid parameters: " + agentArgs);
+ }
+
+ int port = Integer.parseInt(stok.nextToken());
+ TRC.fine("Port: " + port);
+ String clojurePath = stok.nextToken();
+ String serverPath = stok.nextToken();
+
+ boolean clojureLoaded = isClojureLoaded();
+ TRC.fine("Clojure is " + (clojureLoaded ? "" : "not ") + "loaded");
+
+ List<URL> urls;
+ if (clojureLoaded) {
+ urls = getJarUrls(serverPath);
+ } else {
+ urls = getJarUrls(clojurePath, serverPath);
+ }
+
+ ClassLoader old = pushClassLoader(urls);
+ try {
+ if (!clojureLoaded) { // if clojure wasn't loaded before, print current status
+ TRC.fine("Clojure is " + (isClojureLoaded() ? "" : "not ") + "loaded");
+ }
+ startRepl(port);
+ } finally {
+ popClassLoader(old);
+ }
+ }
+
+ private static List<URL> getJarUrls(String... paths) {
+ List<URL> urls = new ArrayList<URL>();
+ try {
+ for (String path : paths) {
+ URL url = new File(path).toURI().toURL();
+ urls.add(url);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return urls;
+ }
+
+ private static void startRepl(int port) {
+ // avoids making load-time references to Clojure classes from the system classloader
+ try {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ Class<?> repl = Class.forName("net.djpowell.liverepl.server.Repl", true, cl);
+ Method method = repl.getMethod("main", InetAddress.class, Integer.TYPE);
+ method.invoke(null, Main.LOCALHOST, port);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static final Logger TRC = Logger.getLogger(Agent.class.getName());
+
+}
70 liverepl-agent/src/net/djpowell/liverepl/client/Console.java
@@ -0,0 +1,70 @@
+package net.djpowell.liverepl.client;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.logging.Logger;
+
+public class Console {
+
+ private static String NEWLINE = System.getProperty("line.separator");
+
+ public static void main(InetAddress host, int port) throws Exception {
+ final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
+ final Writer cout = new OutputStreamWriter(System.out);
+ Socket s = new Socket(host, port);
+ try {
+ final Reader sin = new InputStreamReader(s.getInputStream());
+ final Writer sout = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
+
+ Thread ch = new Thread("ConsoleHandler") {
+ @Override
+ public void run() {
+ try {
+ for (;;) {
+ // use line-based i/o for reading from the keyboard
+ String line = cin.readLine();
+ sout.write(line + NEWLINE);
+ sout.flush();
+ }
+ } catch (IOException e) {
+ TRC.fine(e.getMessage());
+ }
+ }
+ };
+ ch.start();
+
+ Thread sh = new Thread("SocketHandler") {
+ @Override
+ public void run() {
+ try {
+ for (;;) {
+ // use character based i/o for printing server responses
+ char c = (char) sin.read();
+ cout.write(c);
+ cout.flush();
+ }
+ } catch (IOException e) {
+ TRC.fine(e.getMessage());
+ }
+ }
+ };
+ sh.start();
+
+ // block until both threads finish
+ sh.join();
+ ch.join();
+ } finally {
+ s.close();
+ }
+ }
+
+ private static final Logger TRC = Logger.getLogger(Console.class.getName());
+
+}
77 liverepl-agent/src/net/djpowell/liverepl/client/Main.java
@@ -0,0 +1,77 @@
+package net.djpowell.liverepl.client;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.UnknownHostException;
+import java.util.logging.Logger;
+
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
+
+public class Main {
+
+ // bind to localhost to keep things more secure
+ public static final InetAddress LOCALHOST;
+ static {
+ try {
+ LOCALHOST = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int getFreePort(InetAddress host) {
+ // open a server socket on a random port, then close it
+ // so that we can tell the server to listen on that port
+ // and we will know which port to connect to
+ try {
+ ServerSocket server = new ServerSocket(0, 0, LOCALHOST);
+ int port = server.getLocalPort();
+ server.close();
+ return port;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void listPids() {
+ System.out.println();
+ System.out.println("liverepl");
+ System.out.println("Usage: liverepl <pid>");
+ System.out.println();
+ System.out.println("List of Java processes");
+ System.out.format("%1$-6s %2$.60s%n", "pid", "Details");
+ for (VirtualMachineDescriptor vmd : VirtualMachine.list()) {
+ System.out.format("%1$-6s %2$.60s%n", vmd.id(), vmd.displayName());
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ // Usage: <clojurepath> <agentjarpath> <serverjarpath> <jvmpid>
+ if (args.length != 4) {
+ listPids();
+ System.exit(0);
+ }
+ String clojurepath = args[0];
+ String agentpath = args[1];
+ String serverpath = args[2];
+ String pid = args[3];
+ TRC.fine("Attaching to pid: " + pid);
+ VirtualMachine vm = VirtualMachine.attach(pid);
+
+ int port = getFreePort(LOCALHOST);
+ // start the agent, which will create the server socket, then return
+ String agentArgs = String.valueOf(port) + "\n" + clojurepath + "\n" + serverpath;
+ vm.loadAgent(agentpath, agentArgs);
+
+ // start the code that will connect to the server socket
+ Console.main(LOCALHOST, port);
+
+ // the server will shutdown when the client disconnects
+ vm.detach();
+ }
+
+ private static final Logger TRC = Logger.getLogger(Main.class.getName());
+
+}
33 liverepl-server/build.xml
@@ -0,0 +1,33 @@
+<project name="liverepl-server" basedir="." default="jar">
+
+ <target name="clean" description="delete build and classes">
+ <delete dir="./build" />
+ <delete dir="./classes" />
+ </target>
+
+ <target name="compile">
+
+ <echo message="============================================================" />
+ <echo message="Ensure that clojure.jar is copied to ./liverepl-serer/lib" />
+ <echo message="============================================================" />
+
+ <mkdir dir="./classes" />
+ <javac debug="yes"
+ srcdir="./src"
+ destdir="./classes"
+ includes="**/*.java">
+ <classpath>
+ <fileset dir="./lib" includes="*.jar" />
+ </classpath>
+ </javac>
+ </target>
+
+ <target name="jar" description="build liverepl-server.jar" depends="compile">
+ <mkdir dir="./build" />
+ <jar destfile="build/liverepl-server.jar" manifest="src/META-INF/MANIFEST.MF">
+ <fileset dir="./classes" />
+ <fileset dir="./src" includes="**/*.clj" />
+ </jar>
+ </target>
+
+</project>
1  liverepl-server/src/META-INF/MANIFEST.MF
@@ -0,0 +1 @@
+Manifest-Version: 1.0
30 liverepl-server/src/net/djpowell/liverepl/server/Repl.java
@@ -0,0 +1,30 @@
+package net.djpowell.liverepl.server;
+
+import java.net.InetAddress;
+
+import clojure.lang.Namespace;
+import clojure.lang.PersistentArrayMap;
+import clojure.lang.RT;
+import clojure.lang.Symbol;
+import clojure.lang.Var;
+
+public class Repl {
+
+ final static public Symbol REPL_NS = Symbol.create("net.djpowell.liverepl.server.repl");
+ final static public Namespace NS = Namespace.findOrCreate(REPL_NS);
+ final static public Var REQUIRE = Var.intern(RT.CLOJURE_NS, Symbol.create("require"));
+ final static public Var REPL = Var.intern(NS, Symbol.create("repl"));
+
+ public static void main(InetAddress host, int port) throws Exception {
+ // not really needed in clojure 1.1, as context class loaders are the default
+ // required so that clojure can load the repl code
+ Var.pushThreadBindings(new PersistentArrayMap(new Object[] {RT.USE_CONTEXT_CLASSLOADER, Boolean.TRUE}));
+ try {
+ REQUIRE.invoke(REPL_NS);
+ REPL.invoke(port, 0, host);
+ } finally {
+ Var.popThreadBindings();
+ }
+ }
+
+}
50 liverepl-server/src/net/djpowell/liverepl/server/repl.clj
@@ -0,0 +1,50 @@
+(ns net.djpowell.liverepl.server.repl
+ (:require clojure.main)
+ (:import (java.io InputStreamReader BufferedWriter OutputStreamWriter Writer)
+ (java.net ServerSocket Socket)
+ (java.util.logging Logger)
+ (clojure.lang LineNumberingPushbackReader)))
+
+(def new-line (System/getProperty "line.separator"))
+
+(defn repl-caught
+ "Override the default repl-caught to not rely on PrintStream"
+ [e]
+ (.write #^Writer *err* (str (clojure.main/repl-exception e) new-line)))
+
+;; The client should connect immediately after the server starts up,
+;; but in case there is a problem, we will kill the serversocket,
+;; terminating the server, if the client fails to connect.
+(def client-connect-timeout 10000)
+
+(def #^Logger trc (Logger/getLogger (str *ns*)))
+
+(defn repl
+ "Start a single connection repl server"
+ [port backlog host]
+ (let [server (ServerSocket. port backlog host)
+ got-connection (atom false)
+ killer-thread (Thread.
+ (fn []
+ (Thread/sleep client-connect-timeout)
+ (when-not @got-connection
+ (.fine trc "Client connect timeout: terminating server")
+ (.close server)))
+ "Killer Thread")
+ repl-thread (Thread.
+ (fn []
+ (with-open [server server
+ socket (.accept server)
+ in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream socket)))
+ out (BufferedWriter. (OutputStreamWriter. (.getOutputStream socket)))]
+ (swap! got-connection (constantly true))
+ (binding [*in* in
+ *out* out
+ *err* out]
+ (try
+ (clojure.main/repl :caught repl-caught :init #(in-ns 'user))
+ (catch Exception e ; discard errors caused by abrupt disconnection
+ nil)))))
+ "Repl Thread")]
+ (.start killer-thread)
+ (.start repl-thread)))
8 liverepl.bat
@@ -0,0 +1,8 @@
+@echo off
+rem Starter script for Clojure liverepl
+
+set JDK_HOME=c:\jdk
+set LIVEREPL_HOME=%~dp0
+set CLOJURE_JAR=%LIVEREPL_HOME%\clojure.jar
+
+%JDK_HOME%\bin\java -cp %LIVEREPL_HOME%\liverepl-agent.jar;%JDK_HOME%\lib\tools.jar net.djpowell.liverepl.client.Main "%CLOJURE_JAR%" "%LIVEREPL_HOME%\liverepl-agent.jar" "%LIVEREPL_HOME%\liverepl-server.jar" %1
Please sign in to comment.
Something went wrong with that request. Please try again.