package com.redhat.ceylon.compiler.java.test.trace; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import com.redhat.ceylon.compiler.java.test.fordebug.Tracer; import com.redhat.ceylon.compiler.java.test.fordebug.Opcode; import com.sun.jdi.Location; import com.sun.jdi.Method; import com.sun.jdi.ThreadReference; import com.sun.jdi.VirtualMachine; import com.sun.jdi.VirtualMachineManager; import com.sun.jdi.connect.LaunchingConnector; import com.sun.jdi.connect.Connector.Argument; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.MethodEntryEvent; import com.sun.jdi.event.MethodExitEvent; import com.sun.jdi.event.StepEvent; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMDisconnectEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; import com.sun.jdi.request.MethodEntryRequest; import com.sun.jdi.request.MethodExitRequest; import com.sun.jdi.request.StepRequest; public class TracerImpl implements Tracer { private final String mainClass; private String classPath; VirtualMachine vm; InputStream err; InputStream out; EventRequestManager rm; EventQueue eq; public Map<EventRequest, Handler<?>> map = new HashMap<EventRequest, Handler<?>>(1); StringBuffer trace = new StringBuffer(); EventSet events; /** Creates a new tracer for executing the given main class with the given class path */ public TracerImpl(String mainClass, String classPath) { this.mainClass = mainClass; this.classPath = classPath; } @Override public void close() throws Exception { if (err != null) { err.close(); } if (out != null) { out.close(); } if (vm != null) { vm.dispose(); } } /** Echoes the bytes read from the input stream to the given output stream */ void echo(InputStream in, PrintStream out) throws IOException { byte[] buf = new byte[1024]; int avail = in.available(); while (avail > 0) { int read = in.read(buf, 0, Math.min(avail, buf.length)); out.print(new String(buf, 0, read)); avail = in.available(); } } ThreadReference findMainThread(VirtualMachine vm) { ThreadReference main = null; for (ThreadReference thread : vm.allThreads()) { if ("main".equals(thread.name())) { main = thread; break; } } return main; } void log(String event) { trace.append(event).append('\n'); } public void start() throws Exception { VirtualMachineManager vmm = com.sun.jdi.Bootstrap.virtualMachineManager(); LaunchingConnector conn = vmm.defaultConnector(); Map<String, Argument> defaultArguments = conn.defaultArguments(); defaultArguments.get("main").setValue(mainClass); defaultArguments.get("options").setValue("-cp " + classPath); System.out.println(defaultArguments); vm = conn.launch(defaultArguments); err = vm.process().getErrorStream(); out = vm.process().getInputStream(); eq = vm.eventQueue(); rm = vm.eventRequestManager(); outer: while (true) { echo(err, System.err); echo(out, System.out); events = eq.remove(); for (Event event : events) { if (event instanceof VMStartEvent) { System.out.println(event); break outer; } else if (event instanceof VMDisconnectEvent || event instanceof VMDeathEvent) { System.out.println(event); vm = null; rm = null; eq = null; break outer; } } events.resume(); } echo(err, System.err); echo(out, System.out); } public void resume() throws Exception { outer: while (true) { echo(err, System.err); echo(out, System.out); events.resume(); events = eq.remove(); for (Event event : events) { Handler<?> handler = map.get(event.request()); if (handler != null) { switch (((Handler)handler).handle(event)) { case RESUME: break; case SUSPEND: return; } } if (event instanceof VMDisconnectEvent || event instanceof VMDeathEvent) { System.out.println(event); vm = null; rm = null; eq = null; return; } } } } public boolean isVmAlive() { return vm != null; } public String getTrace() { return trace.toString(); } /** Creates and returns a method entry breakpoint */ public MethodEntryImpl methodEntry() { return new MethodEntryImpl(); } public interface Handler<E> { public HandlerResult handle(E event) throws Exception; } public interface EventLogger<E> { void log(E event) throws Exception; } /** Represents a method entry break point */ public class MethodEntryImpl implements MethodEntry,Handler<MethodEntryEvent> { private final MethodEntryRequest request; private String methodName; private HandlerResult result = HandlerResult.RESUME; MethodEntryImpl() { this.request = rm.createMethodEntryRequest(); } /** Add a class name filter for this break point */ public MethodEntry classFilter(String className) { request.addClassFilter(className); return this; } /** Sets the method name filter for this break point */ public MethodEntry methodFilter(String methodName) { this.methodName = methodName; return this; } /** Enables this breakpoint */ public MethodEntry enable() { if (vm == null) { throw new RuntimeException("VM disconnected or died"); } map.put(request, this); this.request.enable(); return this; } /** Disables this breakpoint */ public MethodEntry disable() { if (vm == null) { throw new RuntimeException("VM disconnected or died"); } this.request.disable(); map.remove(request); return this; } /** Sets the result of this breakpoint */ public MethodEntry result(HandlerResult result) { this.result = result; return this; } @Override public HandlerResult handle(MethodEntryEvent event) throws Exception { if (methodName != null && !methodName.equals(event.method().name())) { return HandlerResult.RESUME; } return result; } } /** Creates and returns a method exit breakpoint */ public MethodExit methodExit() { return new MethodExitImpl(); } /** Represents a method exit break point */ public class MethodExitImpl implements MethodExit, Handler<MethodExitEvent> { private final MethodExitRequest request; private String methodName; private HandlerResult result = HandlerResult.RESUME; MethodExitImpl() { this.request = rm.createMethodExitRequest(); } /** Add a class name filter for this break point */ public MethodExit classFilter(String className) { request.addClassFilter(className); return this; } /** Sets the method name filter for this break point */ public MethodExit methodFilter(String methodName) { this.methodName = methodName; return this; } public MethodExit enable() { if (vm == null) { throw new RuntimeException("VM disconnected or died"); } map.put(request, this); this.request.enable(); return this; } public MethodExit disable() { if (vm == null) { throw new RuntimeException("VM disconnected or died"); } this.request.disable(); map.remove(request); return this; } /** Sets the result of this breakpoint */ public MethodExit result(HandlerResult result) { this.result = result; return this; } @Override public HandlerResult handle(MethodExitEvent event) throws Exception { if (methodName != null && !methodName.equals(event.method().name())) { return HandlerResult.RESUME; } return result; } } /** Creates and returns a step breakpoint */ public Step step() { return new StepImpl(); } public class StepImpl implements Step, Handler<StepEvent> { private ThreadReference mainThread; private String within = null; private final StepRequest request; private HandlerResult result = HandlerResult.RESUME; private EventLogger<StepEvent> logger; StepImpl() { super(); mainThread = findMainThread(vm); request = rm.createStepRequest(mainThread, StepRequest.STEP_LINE, StepRequest.STEP_INTO); } public StepImpl within(String within) { this.within = within; return this; } public StepImpl enable() { if (vm == null) { throw new RuntimeException("VM disconnected or died"); } map.put(request, this); request.enable(); return this; } public StepImpl disable() { if (vm == null) { throw new RuntimeException("VM disconnected or died"); } this.request.disable(); map.remove(request); return this; } public StepImpl result(HandlerResult result) { this.result = result; return this; } public StepImpl log() { this.logger = new EventLogger<StepEvent>() { @Override public void log(StepEvent event) throws Exception { Location location = event.location(); StringBuilder sb = new StringBuilder(); for (int ii = 0; ii < mainThread.frames().size(); ii++) { sb.append('.'); } sb.append(location.sourceName()).append(':'); if (location.lineNumber() == -1) { sb.append('?'); } else { sb.append(location.lineNumber()); } sb.append(':'); Method m = location.method(); String atns = m.argumentTypeNames().toString(); sb.append(m.declaringType().name()).append('.').append(m.name()).append('(').append(atns.substring(1, atns.length()-1)).append(')'); sb.append(':'); long ci = location.codeIndex(); if (ci == -1) { sb.append('?'); sb.append(':'); sb.append('?'); } else { sb.append(ci); byte instuction = m.bytecodes()[(int)ci]; sb.append(':'); sb.append(Opcode.values()[(int)instuction & 0xFF]); } TracerImpl.this.log(sb.toString()); } }; return this; } public HandlerResult handle(StepEvent event) throws Exception { Location location = event.location(); if (this.within != null && !this.within.equals(location.sourceName())) { return HandlerResult.RESUME; } if (logger != null) { logger.log(event); } return result; } } }