com.intellij.debugger.engine.DebugProcessEvents.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.debugger.engine.DebugProcessEvents.java

Source

/*
 * 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);
    }
}