Java tutorial
/* * Copyright 2000-2014 JetBrains s.r.o. * * 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.intellij.debugger.engine; import com.intellij.debugger.DebuggerBundle; import com.intellij.debugger.DebuggerInvocationUtil; import com.intellij.debugger.DebuggerManagerEx; import com.intellij.debugger.engine.events.DebuggerCommandImpl; import com.intellij.debugger.engine.events.SuspendContextCommandImpl; import com.intellij.debugger.engine.requests.LocatableEventRequestor; import com.intellij.debugger.engine.requests.MethodReturnValueWatcher; import com.intellij.debugger.impl.DebuggerSession; import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; import com.intellij.debugger.jdi.VirtualMachineProxyImpl; import com.intellij.debugger.requests.Requestor; import com.intellij.debugger.settings.DebuggerSettings; import com.intellij.debugger.ui.DebuggerPanelsManager; import com.intellij.debugger.ui.breakpoints.Breakpoint; import com.intellij.debugger.ui.breakpoints.LineBreakpoint; import com.intellij.execution.configurations.RemoteConnection; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.breakpoints.XBreakpoint; import com.intellij.xdebugger.impl.XDebugSessionImpl; import consulo.internal.com.sun.jdi.InternalException; import consulo.internal.com.sun.jdi.ThreadReference; import consulo.internal.com.sun.jdi.VMDisconnectedException; import consulo.internal.com.sun.jdi.VirtualMachine; import consulo.internal.com.sun.jdi.event.*; import consulo.internal.com.sun.jdi.request.EventRequest; import consulo.internal.com.sun.jdi.request.EventRequestManager; import consulo.internal.com.sun.jdi.request.ThreadDeathRequest; import consulo.internal.com.sun.jdi.request.ThreadStartRequest; /** * @author lex */ public class DebugProcessEvents extends DebugProcessImpl { private static final Logger LOG = Logger.getInstance(DebugProcessEvents.class); private DebuggerEventThread myEventThread; public DebugProcessEvents(Project project) { super(project); } @Override protected void commitVM(final VirtualMachine vm) { super.commitVM(vm); if (vm != null) { vmAttached(); myEventThread = new DebuggerEventThread(); ApplicationManager.getApplication().executeOnPooledThread(myEventThread); } } private static void showStatusText(DebugProcessEvents debugProcess, Event event) { Requestor requestor = debugProcess.getRequestsManager().findRequestor(event.request()); Breakpoint breakpoint = null; if (requestor instanceof Breakpoint) { breakpoint = (Breakpoint) requestor; } String text = debugProcess.getEventText(Pair.create(breakpoint, event)); debugProcess.showStatusText(text); } public String getEventText(Pair<Breakpoint, Event> descriptor) { String text = ""; final Event event = descriptor.getSecond(); final Breakpoint breakpoint = descriptor.getFirst(); if (event instanceof LocatableEvent) { if (breakpoint instanceof LineBreakpoint && !((LineBreakpoint) breakpoint).isVisible()) { text = DebuggerBundle.message("status.stopped.at.cursor"); } else { try { text = breakpoint != null ? breakpoint.getEventMessage(((LocatableEvent) event)) : DebuggerBundle.message("status.generic" + ".breakpoint.reached"); } catch (InternalException e) { text = DebuggerBundle.message("status.generic.breakpoint.reached"); } } } else if (event instanceof VMStartEvent) { text = DebuggerBundle.message("status.process.started"); } else if (event instanceof VMDeathEvent) { text = DebuggerBundle.message("status.process.terminated"); } else if (event instanceof VMDisconnectEvent) { final RemoteConnection connection = getConnection(); final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); final String transportName = DebuggerBundle.getTransportName(connection); text = DebuggerBundle.message("status.disconnected", addressDisplayName, transportName); } return text; } private class DebuggerEventThread implements Runnable { private final VirtualMachineProxyImpl myVmProxy; DebuggerEventThread() { myVmProxy = getVirtualMachineProxy(); } private boolean myIsStopped = false; public synchronized void stopListening() { myIsStopped = true; } private synchronized boolean isStopped() { return myIsStopped; } @Override public void run() { try { EventQueue eventQueue = myVmProxy.eventQueue(); while (!isStopped()) { try { final EventSet eventSet = eventQueue.remove(); final boolean methodWatcherActive = myReturnValueWatcher != null && myReturnValueWatcher.isEnabled(); int processed = 0; for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator.hasNext();) { final Event event = eventIterator.nextEvent(); if (methodWatcherActive) { if (event instanceof MethodExitEvent) { if (myReturnValueWatcher.processMethodExitEvent((MethodExitEvent) event)) { processed++; } continue; } } if (event instanceof ThreadStartEvent) { processed++; final ThreadReference thread = ((ThreadStartEvent) event).thread(); getManagerThread().schedule(new DebuggerCommandImpl() { @Override protected void action() throws Exception { getVirtualMachineProxy().threadStarted(thread); myDebugProcessDispatcher.getMulticaster() .threadStarted(DebugProcessEvents.this, thread); } }); } else if (event instanceof ThreadDeathEvent) { processed++; final ThreadReference thread = ((ThreadDeathEvent) event).thread(); getManagerThread().schedule(new DebuggerCommandImpl() { @Override protected void action() throws Exception { getVirtualMachineProxy().threadStopped(thread); myDebugProcessDispatcher.getMulticaster() .threadStopped(DebugProcessEvents.this, thread); } }); } } if (processed == eventSet.size()) { eventSet.resume(); continue; } getManagerThread().invokeAndWait(new DebuggerCommandImpl() { @Override protected void action() throws Exception { if (eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL && !DebuggerSession.enableBreakpointsDuringEvaluation()) { // check if there is already one request with policy SUSPEND_ALL for (SuspendContextImpl context : getSuspendManager().getEventContexts()) { if (context.getSuspendPolicy() == EventRequest.SUSPEND_ALL) { eventSet.resume(); return; } } } final SuspendContextImpl suspendContext = getSuspendManager() .pushSuspendContext(eventSet); for (EventIterator eventIterator = eventSet.eventIterator(); eventIterator .hasNext();) { final Event event = eventIterator.nextEvent(); //if (LOG.isDebugEnabled()) { // LOG.debug("EVENT : " + event); //} try { if (event instanceof VMStartEvent) { //Sun WTK fails when J2ME when event set is resumed on VMStartEvent processVMStartEvent(suspendContext, (VMStartEvent) event); } else if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) { processVMDeathEvent(suspendContext, event); } else if (event instanceof ClassPrepareEvent) { processClassPrepareEvent(suspendContext, (ClassPrepareEvent) event); } //AccessWatchpointEvent, BreakpointEvent, ExceptionEvent, MethodEntryEvent, MethodExitEvent, //ModificationWatchpointEvent, StepEvent, WatchpointEvent else if (event instanceof StepEvent) { processStepEvent(suspendContext, (StepEvent) event); } else if (event instanceof LocatableEvent) { processLocatableEvent(suspendContext, (LocatableEvent) event); } else if (event instanceof ClassUnloadEvent) { processDefaultEvent(suspendContext); } } catch (VMDisconnectedException e) { LOG.debug(e); } catch (InternalException e) { LOG.info(e); } catch (Throwable e) { LOG.error(e); } } } }); } catch (InternalException e) { LOG.debug(e); } catch (InterruptedException e) { throw e; } catch (VMDisconnectedException e) { throw e; } catch (ProcessCanceledException e) { throw e; } catch (Throwable e) { LOG.debug(e); } } } catch (InterruptedException e) { invokeVMDeathEvent(); } catch (VMDisconnectedException e) { invokeVMDeathEvent(); } finally { Thread.interrupted(); // reset interrupted status } } private void invokeVMDeathEvent() { getManagerThread().invokeAndWait(new DebuggerCommandImpl() { @Override protected void action() throws Exception { SuspendContextImpl suspendContext = getSuspendManager() .pushSuspendContext(EventRequest.SUSPEND_NONE, 1); processVMDeathEvent(suspendContext, null); } }); } } private static void preprocessEvent(SuspendContextImpl suspendContext, ThreadReference thread) { ThreadReferenceProxyImpl oldThread = suspendContext.getThread(); suspendContext.setThread(thread); if (oldThread == null) { //this is the first event in the eventSet that we process suspendContext.getDebugProcess().beforeSuspend(suspendContext); } } private void processVMStartEvent(final SuspendContextImpl suspendContext, VMStartEvent event) { preprocessEvent(suspendContext, event.thread()); if (LOG.isDebugEnabled()) { LOG.debug("enter: processVMStartEvent()"); } showStatusText(this, event); getSuspendManager().voteResume(suspendContext); } private void vmAttached() { DebuggerManagerThreadImpl.assertIsManagerThread(); LOG.assertTrue(!isAttached()); if (myState.compareAndSet(STATE_INITIAL, STATE_ATTACHED)) { final VirtualMachineProxyImpl machineProxy = getVirtualMachineProxy(); final EventRequestManager requestManager = machineProxy.eventRequestManager(); if (machineProxy.canGetMethodReturnValues()) { myReturnValueWatcher = new MethodReturnValueWatcher(requestManager); } final ThreadStartRequest threadStartRequest = requestManager.createThreadStartRequest(); threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE); threadStartRequest.enable(); final ThreadDeathRequest threadDeathRequest = requestManager.createThreadDeathRequest(); threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE); threadDeathRequest.enable(); myDebugProcessDispatcher.getMulticaster().processAttached(this); // breakpoints should be initialized after all processAttached listeners work ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { XDebugSession session = getSession().getXDebugSession(); if (session != null) { session.initBreakpoints(); } } }); final String addressDisplayName = DebuggerBundle.getAddressDisplayName(getConnection()); final String transportName = DebuggerBundle.getTransportName(getConnection()); showStatusText(DebuggerBundle.message("status.connected", addressDisplayName, transportName)); if (LOG.isDebugEnabled()) { LOG.debug("leave: processVMStartEvent()"); } } } private void processVMDeathEvent(SuspendContextImpl suspendContext, Event event) { try { preprocessEvent(suspendContext, null); cancelRunToCursorBreakpoint(); } finally { if (myEventThread != null) { myEventThread.stopListening(); myEventThread = null; } closeProcess(false); } if (event != null) { showStatusText(this, event); } } private void processClassPrepareEvent(SuspendContextImpl suspendContext, ClassPrepareEvent event) { preprocessEvent(suspendContext, event.thread()); if (LOG.isDebugEnabled()) { LOG.debug("Class prepared: " + event.referenceType().name()); } suspendContext.getDebugProcess().getRequestsManager().processClassPrepared(event); getSuspendManager().voteResume(suspendContext); } private void processStepEvent(SuspendContextImpl suspendContext, StepEvent event) { final ThreadReference thread = event.thread(); //LOG.assertTrue(thread.isSuspended()); preprocessEvent(suspendContext, thread); //noinspection HardCodedStringLiteral RequestHint hint = (RequestHint) event.request().getProperty("hint"); deleteStepRequests(event.thread()); boolean shouldResume = false; final Project project = getProject(); if (hint != null) { final int nextStepDepth = hint.getNextStepDepth(suspendContext); if (nextStepDepth != RequestHint.STOP) { final ThreadReferenceProxyImpl threadProxy = suspendContext.getThread(); doStep(suspendContext, threadProxy, nextStepDepth, hint); shouldResume = true; } if (!shouldResume && hint.isRestoreBreakpoints()) { DebuggerManagerEx.getInstanceEx(project).getBreakpointManager().enableBreakpoints(this); } } if (shouldResume) { getSuspendManager().voteResume(suspendContext); } else { showStatusText(""); if (myReturnValueWatcher != null) { myReturnValueWatcher.disable(); } getSuspendManager().voteSuspend(suspendContext); if (hint != null) { final MethodFilter methodFilter = hint.getMethodFilter(); if (methodFilter instanceof NamedMethodFilter && !hint.wasStepTargetMethodMatched()) { final String message = "Method <b>" + ((NamedMethodFilter) methodFilter).getMethodName() + "()</b> has not been called"; XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(message, MessageType.INFO) .notify(project); } } } } private void processLocatableEvent(final SuspendContextImpl suspendContext, final LocatableEvent event) { if (myReturnValueWatcher != null && event instanceof MethodExitEvent) { if (myReturnValueWatcher.processMethodExitEvent(((MethodExitEvent) event))) { return; } } ThreadReference thread = event.thread(); //LOG.assertTrue(thread.isSuspended()); preprocessEvent(suspendContext, thread); //we use schedule to allow processing other events during processing this one //this is especially necessary if a method is breakpoint condition getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) { @Override public void contextAction() throws Exception { final SuspendManager suspendManager = getSuspendManager(); SuspendContextImpl evaluatingContext = SuspendManagerUtil.getEvaluatingContext(suspendManager, getSuspendContext().getThread()); if (evaluatingContext != null && !DebuggerSession.enableBreakpointsDuringEvaluation()) { // is inside evaluation, so ignore any breakpoints suspendManager.voteResume(suspendContext); return; } final LocatableEventRequestor requestor = (LocatableEventRequestor) getRequestsManager() .findRequestor(event.request()); boolean resumePreferred = requestor != null && DebuggerSettings.SUSPEND_NONE.equals(requestor.getSuspendPolicy()); boolean requestHit; try { requestHit = (requestor != null) && requestor.processLocatableEvent(this, event); } catch (final LocatableEventRequestor.EventProcessingException ex) { if (LOG.isDebugEnabled()) { LOG.debug(ex.getMessage()); } final boolean[] considerRequestHit = new boolean[] { true }; DebuggerInvocationUtil.invokeAndWait(getProject(), new Runnable() { @Override public void run() { DebuggerPanelsManager.getInstance(getProject()).toFront(mySession); final String displayName = requestor instanceof Breakpoint ? ((Breakpoint) requestor).getDisplayName() : requestor.getClass().getSimpleName(); final String message = DebuggerBundle.message( "error.evaluating.breakpoint.condition.or.action", displayName, ex.getMessage()); considerRequestHit[0] = Messages.showYesNoDialog(getProject(), message, ex.getTitle(), Messages.getQuestionIcon()) == Messages.YES; } }, ModalityState.NON_MODAL); requestHit = considerRequestHit[0]; resumePreferred = !requestHit; } if (requestHit && requestor instanceof Breakpoint) { // if requestor is a breakpoint and this breakpoint was hit, no matter its suspend policy ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { XDebugSession session = getSession().getXDebugSession(); if (session != null) { XBreakpoint breakpoint = ((Breakpoint) requestor).getXBreakpoint(); if (breakpoint != null) { ((XDebugSessionImpl) session).processDependencies(breakpoint); } } } }); } if (!requestHit || resumePreferred) { suspendManager.voteResume(suspendContext); } else { if (myReturnValueWatcher != null) { myReturnValueWatcher.disable(); } //if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL) { // // there could be explicit resume as a result of call to voteSuspend() // // e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_ // // resuming and all breakpoints in other threads will be ignored. // // As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens // myBreakpointManager.applyThreadFilter(DebugProcessEvents.this, event.thread()); //} suspendManager.voteSuspend(suspendContext); showStatusText(DebugProcessEvents.this, event); } } }); } private void processDefaultEvent(SuspendContextImpl suspendContext) { preprocessEvent(suspendContext, null); getSuspendManager().voteResume(suspendContext); } }