package jetbrains.mps.debugger.java.runtime.engine.events; /*Generated by MPS */ import jetbrains.mps.debugger.java.runtime.engine.concurrent.ManagerThread; import com.sun.jdi.VirtualMachine; import java.util.concurrent.atomic.AtomicInteger; import jetbrains.mps.debugger.java.runtime.engine.RequestManager; import jetbrains.mps.debugger.java.runtime.engine.DebugProcessMulticaster; import jetbrains.mps.debugger.java.runtime.engine.SystemMessagesReporter; import jetbrains.mps.debug.api.BreakpointManagerComponent; import jetbrains.mps.debug.api.IDebuggableFramesSelector; import com.intellij.openapi.project.Project; import java.util.Map; import com.sun.jdi.ThreadReference; import jetbrains.mps.internal.collections.runtime.MapSequence; import java.util.HashMap; import org.jetbrains.annotations.NotNull; import jetbrains.mps.debugger.java.runtime.engine.concurrent.Commands; import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes; import jetbrains.mps.debugger.java.runtime.engine.requests.StepRequestor; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.StepEvent; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.StepRequest; import com.sun.jdi.event.LocatableEvent; import jetbrains.mps.debugger.java.runtime.engine.requests.LocatableEventRequestor; import jetbrains.mps.debugger.java.runtime.breakpoints.JavaBreakpoint; import com.sun.jdi.AbsentInformationException; import com.intellij.openapi.application.ApplicationManager; import org.jetbrains.annotations.Nullable; import java.util.concurrent.atomic.AtomicReference; import com.intellij.openapi.progress.util.ProgressWindowWithNotification; import com.intellij.openapi.progress.util.ProgressIndicatorListenerAdapter; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProcessCanceledException; import jetbrains.mps.debugger.java.runtime.engine.DebugProcessListener; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.Event; import jetbrains.mps.internal.collections.runtime.SetSequence; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMDisconnectEvent; public class EventsProcessor { protected static final int STATE_INITIAL = 0; protected static final int STATE_ATTACHED = 1; protected static final int STATE_DETACHING = 2; protected static final int STATE_DETACHED = 3; private final ManagerThread myManagerThread = new ManagerThread(); private EventsProcessor.EventProcessorRunnable myRunnable = new EventsProcessor.EventProcessorRunnable(); private VirtualMachine myVirtualMachine; protected final AtomicInteger myState = new AtomicInteger(STATE_INITIAL); private RequestManager myRequestManager; private final ContextManager myContextManager = new ContextManager(); private final DebugProcessMulticaster myMulticaster = new DebugProcessMulticaster(); private final SystemMessagesReporter myReporter = new SystemMessagesReporter(myMulticaster); private final BreakpointManagerComponent myBreakpointManager; private IDebuggableFramesSelector myFramesSelector; private final Project myProject; private final Map<ThreadReference, Integer> myEvaluatedThreads = MapSequence.fromMap(new HashMap<ThreadReference, Integer>()); public EventsProcessor(Project project, BreakpointManagerComponent breakpointsManagerComponent) { myProject = project; myBreakpointManager = breakpointsManagerComponent; myRequestManager = new RequestManager(this); // todo? } public void commitVm(@NotNull VirtualMachine vm) { myVirtualMachine = vm; if (myState.compareAndSet(STATE_INITIAL, STATE_ATTACHED)) { myMulticaster.processAttached(this); } new Thread(myRunnable, "Debug Events Processor Thread").start(); } public boolean isAttached() { return myState.get() == STATE_ATTACHED; } private void closeProcess(boolean byUser) { ManagerThread.assertIsMangerThread(); if (myState.compareAndSet(STATE_INITIAL, STATE_DETACHING) || myState.compareAndSet(STATE_ATTACHED, STATE_DETACHING)) { myVirtualMachine = null; try { myManagerThread.close(); } finally { myState.set(STATE_DETACHED); myMulticaster.processDetached(this, byUser); } } } public void pause() { myManagerThread.invoke(Commands.fromClosure(new _FunctionTypes._void_P0_E0() { public void invoke() { myVirtualMachine.suspend(); UserContext context = new UserContext(EventsProcessor.this); myContextManager.pauseUserContext(context); myMulticaster.paused(context); } })); } public void resume(@NotNull final Context context) { myManagerThread.invoke(Commands.fromClosure(new _FunctionTypes._void_P0_E0() { public void invoke() { myContextManager.resume(context); myMulticaster.resumed(context); } })); } public void step(@NotNull final EventsProcessor.StepKind kind, @NotNull final Context context) { myManagerThread.invoke(Commands.fromClosure(new _FunctionTypes._void_P0_E0() { public void invoke() { int jdiType = kind.getJdiType(); addNewStepRequest(new StepRequestor(context.getThread(), jdiType, myFramesSelector), jdiType, context.getThread(), context.getSuspendPolicy()); resume(context); } })); } public void stop(final boolean terminate) { myManagerThread.invoke(Commands.fromClosure(new _FunctionTypes._void_P0_E0() { public void invoke() { if (isAttached()) { if (terminate) { myVirtualMachine.exit(-1); } else { // some VM's (like IBM VM 1.4.2 bundled with WebSpere) does not // resume threads on dispose() like it should myVirtualMachine.resume(); myVirtualMachine.dispose(); } } else { // todo DebugProcessImpl.stopConnecting closeProcess(true); } } })); } private void processVmDeathEvent() { ManagerThread.assertIsMangerThread(); if (myRunnable != null) { myRunnable.stop(); myRunnable = null; } closeProcess(false); } private void processClassPrepareEvent(EventContext context, ClassPrepareEvent event) { ManagerThread.assertIsMangerThread(); myRequestManager.processClassPrepared(event); myContextManager.voteResume(context); } private void processStepEvent(EventContext context, StepEvent event) { myRequestManager.deleteStepRequests(); EventRequest request = event.request(); if (request instanceof StepRequest) { StepRequest stepRequest = (StepRequest) request; StepRequestor requestor = (StepRequestor) myRequestManager.findRequestor(stepRequest); int nextStep = requestor.nextStep(event); if (nextStep == StepRequestor.STOP) { boolean paused = myContextManager.votePause(context); if (paused) { myMulticaster.paused(context); } return; } else { addNewStepRequest(requestor, nextStep, event.thread(), context.getSuspendPolicy()); } } myContextManager.voteResume(context); } private void addNewStepRequest(StepRequestor stepRequestor, int stepType, ThreadReference threadReference, int suspendPolicy) { ManagerThread.assertIsMangerThread(); StepRequest stepRequest = myRequestManager.createStepRequest(stepRequestor, stepType, threadReference, suspendPolicy); // TODO request filters should be configured by user // this particular list was taken from idea debugger settings in order to fix MPS-8725 stepRequest.addClassExclusionFilter("java.*"); stepRequest.addClassExclusionFilter("javax.*"); stepRequest.addClassExclusionFilter("org.omg.*"); stepRequest.addClassExclusionFilter("sun.*"); stepRequest.addClassExclusionFilter("junit.*"); stepRequest.addClassExclusionFilter("com.sun.*"); // TODO also might wanna let user to exclude constructors, classloaders, getters, // synthetic methods (whatever synthetic methods are). // see idea debugger settings for the full list myRequestManager.enableRequest(stepRequest); } private void processLocatableEvent(final EventContext context, final LocatableEvent event) { ManagerThread.assertIsMangerThread(); // if inside evaluation, resume final ThreadReference thread = event.thread(); if (isEvaluated(thread)) { myContextManager.voteResume(context); return; } final LocatableEventRequestor requestor = (LocatableEventRequestor) myRequestManager.findRequestor(event.request()); // if no requestor or suspend none resume if (requestor == null || EventRequest.SUSPEND_NONE == requestor.getSuspendPolicy()) { myContextManager.voteResume(context); } // requestor may evaluate something inside, like a condition or an expression to print scheduleEvaluation(new _FunctionTypes._void_P0_E0() { public void invoke() { boolean resume = true; try { resume = !(requestor.isRequestHitByEvent(context, event)); } finally { if (resume) { myContextManager.voteResume(context); } else { try { if (requestor instanceof JavaBreakpoint && ((JavaBreakpoint) requestor).isLogMessage()) { // todo move to java breakpoint? myReporter.reportInformation("Breakpoint hit: " + ((JavaBreakpoint) requestor).getPresentation() + " " + event.location().sourceName() + ":" + event.location().lineNumber()); } } catch (AbsentInformationException ignore) { } finally { boolean paused = myContextManager.votePause(context); if (paused) { myMulticaster.paused(context); } } } } } }, thread); } public void scheduleEvaluation(final _FunctionTypes._void_P0_E0 evaluationCommand, final ThreadReference threadToEvaluateIn) { ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { public void run() { startEvaluation(threadToEvaluateIn); try { evaluationCommand.invoke(); } finally { finishEvaluation(threadToEvaluateIn); } } }); } @Nullable public <R> R invokeEvaluationUnderProgress(final _FunctionTypes._return_P0_E0<? extends R> evaluationCommand, final ThreadReference threadToEvaluateIn) { ApplicationManager.getApplication().assertIsDispatchThread(); final AtomicReference<R> resultReference = new AtomicReference(); final ProgressWindowWithNotification progress = new ProgressWindowWithNotification(true, false, myProject, null, null); progress.setTitle("Evaluating"); progress.addListener(new ProgressIndicatorListenerAdapter() { @Override public void cancelled() { progress.stop(); } }); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { public void run() { try { ProgressManager.getInstance().runProcess(new Runnable() { public void run() { startEvaluation(threadToEvaluateIn); try { resultReference.set(evaluationCommand.invoke()); } finally { finishEvaluation(threadToEvaluateIn); } } }, progress); } catch (ProcessCanceledException e) { progress.cancel(); } catch (RuntimeException e) { progress.cancel(); throw e; } } }); progress.startBlocking(); return resultReference.get(); } public void schedule(_FunctionTypes._void_P0_E0 command, _FunctionTypes._void_P0_E0 cancel) { myManagerThread.schedule(Commands.fromClosure(command, cancel)); } public void schedule(_FunctionTypes._void_P0_E0 command) { myManagerThread.schedule(Commands.fromClosure(command)); } public void invoke(_FunctionTypes._void_P0_E0 command) { myManagerThread.invoke(Commands.fromClosure(command)); } public RequestManager getRequestManager() { return myRequestManager; } public ContextManager getContextManager() { return myContextManager; } public BreakpointManagerComponent getBreakpointManager() { return myBreakpointManager; } public VirtualMachine getVirtualMachine() { return myVirtualMachine; } public void addDebugProcessListener(DebugProcessListener listener) { myMulticaster.addListener(listener); } public void removeDebugProcessListener(DebugProcessListener listener) { myMulticaster.removeListener(listener); } public void setDebuggableFramesSelector(IDebuggableFramesSelector framesSelector) { myFramesSelector = framesSelector; } public SystemMessagesReporter getSystemMessagesReporter() { return myReporter; } public DebugProcessMulticaster getMulticaster() { // todo review all this getters, really return myMulticaster; } private synchronized void startEvaluation(@NotNull ThreadReference threadReference) { Integer evaluated = MapSequence.fromMap(myEvaluatedThreads).get(threadReference); if (evaluated == null) { evaluated = 0; } MapSequence.fromMap(myEvaluatedThreads).put(threadReference, evaluated + 1); } private synchronized void finishEvaluation(@NotNull ThreadReference threadReference) { Integer evaluated = MapSequence.fromMap(myEvaluatedThreads).get(threadReference); assert evaluated != null && evaluated > 0; if (evaluated == 1) { MapSequence.fromMap(myEvaluatedThreads).removeKey(threadReference); } else { MapSequence.fromMap(myEvaluatedThreads).put(threadReference, evaluated - 1); } } private synchronized boolean isEvaluated(@NotNull ThreadReference threadReference) { return MapSequence.fromMap(myEvaluatedThreads).containsKey(threadReference) && MapSequence.fromMap(myEvaluatedThreads).get(threadReference) > 0; } public static boolean isOnPooledThread() { // it is sufficient to check for this two return !(ManagerThread.isManagerThread()) && !(ApplicationManager.getApplication().isDispatchThread()); } public class EventProcessorRunnable implements Runnable { private volatile boolean myIsStopped = false; public EventProcessorRunnable() { } @Override public void run() { try { EventQueue eventQueue = myVirtualMachine.eventQueue(); while (!(myIsStopped)) { final EventSet events = eventQueue.remove(); myManagerThread.invokeAndWait(Commands.fromClosure(new _FunctionTypes._void_P0_E0() { public void invoke() { EventContext context = new EventContext(EventsProcessor.this, events); for (Event event : SetSequence.fromSet(events)) { if (event instanceof VMDeathEvent) { processVmDeathEvent(); } else if (event instanceof VMDisconnectEvent) { processVmDeathEvent(); } else if (event instanceof ClassPrepareEvent) { processClassPrepareEvent(context, (ClassPrepareEvent) event); } else if (event instanceof StepEvent) { processStepEvent(context, (StepEvent) event); } else if (event instanceof LocatableEvent) { processLocatableEvent(context, (LocatableEvent) event); } else { myContextManager.voteResume(context); } } } })); } } catch (InterruptedException e) { myManagerThread.invokeAndWait(Commands.fromClosure(new _FunctionTypes._void_P0_E0() { public void invoke() { processVmDeathEvent(); } })); } } public void stop() { myIsStopped = true; } public boolean isStopped() { return myIsStopped; } } public enum StepKind { Over(StepRequest.STEP_OVER), Into(StepRequest.STEP_INTO), Out(StepRequest.STEP_OUT); private final int myJdiType; private StepKind(int jdiType) { myJdiType = jdiType; } public int getJdiType() { return myJdiType; } } }