moved REMOTE_SESSION_KEY init into base attachVirtualMachine
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / impl / DebuggerManagerImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.debugger.impl;
17
18 import com.intellij.debugger.*;
19 import com.intellij.debugger.apiAdapters.TransportServiceWrapper;
20 import com.intellij.debugger.engine.*;
21 import com.intellij.debugger.settings.DebuggerSettings;
22 import com.intellij.debugger.ui.GetJPDADialog;
23 import com.intellij.debugger.ui.breakpoints.BreakpointManager;
24 import com.intellij.debugger.ui.tree.render.BatchEvaluator;
25 import com.intellij.execution.ExecutionException;
26 import com.intellij.execution.ExecutionResult;
27 import com.intellij.execution.configurations.JavaParameters;
28 import com.intellij.execution.configurations.RemoteConnection;
29 import com.intellij.execution.configurations.RunProfileState;
30 import com.intellij.execution.process.KillableColoredProcessHandler;
31 import com.intellij.execution.process.ProcessAdapter;
32 import com.intellij.execution.process.ProcessEvent;
33 import com.intellij.execution.process.ProcessHandler;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.components.PersistentStateComponent;
36 import com.intellij.openapi.components.State;
37 import com.intellij.openapi.components.Storage;
38 import com.intellij.openapi.components.StoragePathMacros;
39 import com.intellij.openapi.diagnostic.Logger;
40 import com.intellij.openapi.editor.colors.EditorColorsListener;
41 import com.intellij.openapi.editor.colors.EditorColorsManager;
42 import com.intellij.openapi.editor.colors.EditorColorsScheme;
43 import com.intellij.openapi.extensions.Extensions;
44 import com.intellij.openapi.progress.ProgressManager;
45 import com.intellij.openapi.project.Project;
46 import com.intellij.openapi.projectRoots.JavaSdk;
47 import com.intellij.openapi.projectRoots.JavaSdkVersion;
48 import com.intellij.openapi.projectRoots.JdkUtil;
49 import com.intellij.openapi.projectRoots.Sdk;
50 import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
51 import com.intellij.openapi.startup.StartupManager;
52 import com.intellij.openapi.util.SystemInfo;
53 import com.intellij.openapi.util.WriteExternalException;
54 import com.intellij.openapi.util.text.StringUtil;
55 import com.intellij.openapi.vfs.VirtualFile;
56 import com.intellij.psi.PsiClass;
57 import com.intellij.util.EventDispatcher;
58 import com.intellij.util.Function;
59 import com.intellij.util.containers.ContainerUtil;
60 import org.jdom.Element;
61 import org.jetbrains.annotations.NonNls;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64
65 import javax.swing.*;
66 import java.io.File;
67 import java.util.*;
68 import java.util.jar.Attributes;
69
70 @State(name = "DebuggerManager", storages = {@Storage(StoragePathMacros.WORKSPACE_FILE)})
71 public class DebuggerManagerImpl extends DebuggerManagerEx implements PersistentStateComponent<Element> {
72   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.DebuggerManagerImpl");
73
74   private final Project myProject;
75   private final HashMap<ProcessHandler, DebuggerSession> mySessions = new HashMap<>();
76   private final BreakpointManager myBreakpointManager;
77   private final List<NameMapper> myNameMappers = ContainerUtil.createLockFreeCopyOnWriteList();
78   private final List<Function<DebugProcess, PositionManager>> myCustomPositionManagerFactories =
79     new ArrayList<>();
80
81   private final EventDispatcher<DebuggerManagerListener> myDispatcher = EventDispatcher.create(DebuggerManagerListener.class);
82   private final MyDebuggerStateManager myDebuggerStateManager = new MyDebuggerStateManager();
83
84   private final DebuggerContextListener mySessionListener = new DebuggerContextListener() {
85     @Override
86     public void changeEvent(@NotNull DebuggerContextImpl newContext, DebuggerSession.Event event) {
87
88       final DebuggerSession session = newContext.getDebuggerSession();
89       if (event == DebuggerSession.Event.PAUSE && myDebuggerStateManager.myDebuggerSession != session) {
90         // if paused in non-active session; switch current session
91         myDebuggerStateManager.setState(newContext, session != null? session.getState() : DebuggerSession.State.DISPOSED, event, null);
92         return;
93       }
94
95       if (myDebuggerStateManager.myDebuggerSession == session) {
96         myDebuggerStateManager.fireStateChanged(newContext, event);
97       }
98       if (event == DebuggerSession.Event.ATTACHED) {
99         myDispatcher.getMulticaster().sessionAttached(session);
100       }
101       else if (event == DebuggerSession.Event.DETACHED) {
102         myDispatcher.getMulticaster().sessionDetached(session);
103       }
104       else if (event == DebuggerSession.Event.DISPOSE) {
105         dispose(session);
106         if (myDebuggerStateManager.myDebuggerSession == session) {
107           myDebuggerStateManager
108             .setState(DebuggerContextImpl.EMPTY_CONTEXT, DebuggerSession.State.DISPOSED, DebuggerSession.Event.DISPOSE, null);
109         }
110       }
111     }
112   };
113   @NonNls private static final String DEBUG_KEY_NAME = "idea.xdebug.key";
114
115   @Override
116   public void addClassNameMapper(final NameMapper mapper) {
117     myNameMappers.add(mapper);
118   }
119
120   @Override
121   public void removeClassNameMapper(final NameMapper mapper) {
122     myNameMappers.remove(mapper);
123   }
124
125   @Override
126   public String getVMClassQualifiedName(@NotNull final PsiClass aClass) {
127     for (NameMapper nameMapper : myNameMappers) {
128       final String qName = nameMapper.getQualifiedName(aClass);
129       if (qName != null) {
130         return qName;
131       }
132     }
133     return aClass.getQualifiedName();
134   }
135
136   @Override
137   public void addDebuggerManagerListener(DebuggerManagerListener listener) {
138     myDispatcher.addListener(listener);
139   }
140
141   @Override
142   public void removeDebuggerManagerListener(DebuggerManagerListener listener) {
143     myDispatcher.removeListener(listener);
144   }
145
146   public DebuggerManagerImpl(Project project, StartupManager startupManager, EditorColorsManager colorsManager) {
147     myProject = project;
148     myBreakpointManager = new BreakpointManager(myProject, startupManager, this);
149     if (!project.isDefault()) {
150       colorsManager.addEditorColorsListener(new EditorColorsListener() {
151         @Override
152         public void globalSchemeChange(EditorColorsScheme scheme) {
153           getBreakpointManager().updateBreakpointsUI();
154         }
155       }, project);
156     }
157   }
158
159   @Nullable
160   @Override
161   public DebuggerSession getSession(DebugProcess process) {
162     ApplicationManager.getApplication().assertIsDispatchThread();
163     for (final DebuggerSession debuggerSession : getSessions()) {
164       if (process == debuggerSession.getProcess()) return debuggerSession;
165     }
166     return null;
167   }
168
169   @NotNull
170   @Override
171   public Collection<DebuggerSession> getSessions() {
172     synchronized (mySessions) {
173       final Collection<DebuggerSession> values = mySessions.values();
174       return values.isEmpty() ? Collections.emptyList() : new ArrayList<>(values);
175     }
176   }
177
178   @Override
179   public void disposeComponent() {
180   }
181
182   @Override
183   public void initComponent() {
184   }
185
186   @Override
187   public void projectClosed() {
188   }
189
190   @Override
191   public void projectOpened() {
192     myBreakpointManager.init();
193   }
194
195   @Nullable
196   @Override
197   public Element getState() {
198     Element state = new Element("state");
199     myBreakpointManager.writeExternal(state);
200     return state;
201   }
202
203   @Override
204   public void loadState(Element state) {
205     myBreakpointManager.readExternal(state);
206   }
207
208   public void writeExternal(Element element) throws WriteExternalException {
209     myBreakpointManager.writeExternal(element);
210   }
211
212   @Override
213   @Nullable
214   public DebuggerSession attachVirtualMachine(@NotNull DebugEnvironment environment) throws ExecutionException {
215     ApplicationManager.getApplication().assertIsDispatchThread();
216     final DebugProcessEvents debugProcess = new DebugProcessEvents(myProject);
217     debugProcess.addDebugProcessListener(new DebugProcessAdapter() {
218       @Override
219       public void processAttached(final DebugProcess process) {
220         process.removeDebugProcessListener(this);
221         for (Function<DebugProcess, PositionManager> factory : myCustomPositionManagerFactories) {
222           final PositionManager positionManager = factory.fun(process);
223           if (positionManager != null) {
224             process.appendPositionManager(positionManager);
225           }
226         }
227         for (PositionManagerFactory factory : Extensions.getExtensions(PositionManagerFactory.EP_NAME, myProject)) {
228           final PositionManager manager = factory.createPositionManager(debugProcess);
229           if (manager != null) {
230             process.appendPositionManager(manager);
231           }
232         }
233       }
234
235       @Override
236       public void processDetached(final DebugProcess process, final boolean closedByUser) {
237         debugProcess.removeDebugProcessListener(this);
238       }
239
240       @Override
241       public void attachException(final RunProfileState state,
242                                   final ExecutionException exception,
243                                   final RemoteConnection remoteConnection) {
244         debugProcess.removeDebugProcessListener(this);
245       }
246     });
247     DebuggerSession session = DebuggerSession.create(environment.getSessionName(), debugProcess, environment);
248     ExecutionResult executionResult = session.getProcess().getExecutionResult();
249     if (executionResult == null) {
250       return null;
251     }
252     session.getContextManager().addListener(mySessionListener);
253     getContextManager()
254       .setState(DebuggerContextUtil.createDebuggerContext(session, session.getContextManager().getContext().getSuspendContext()),
255                 session.getState(), DebuggerSession.Event.CONTEXT, null);
256
257     final ProcessHandler processHandler = executionResult.getProcessHandler();
258
259     synchronized (mySessions) {
260       mySessions.put(processHandler, session);
261     }
262
263     if (!(processHandler instanceof RemoteDebugProcessHandler)) {
264       // add listener only to non-remote process handler:
265       // on Unix systems destroying process does not cause VMDeathEvent to be generated,
266       // so we need to call debugProcess.stop() explicitly for graceful termination.
267       // RemoteProcessHandler on the other hand will call debugProcess.stop() as a part of destroyProcess() and detachProcess() implementation,
268       // so we shouldn't add the listener to avoid calling stop() twice
269       processHandler.addProcessListener(new ProcessAdapter() {
270         @Override
271         public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
272           final DebugProcessImpl debugProcess = getDebugProcess(event.getProcessHandler());
273           if (debugProcess != null) {
274             // if current thread is a "debugger manager thread", stop will execute synchronously
275             // it is KillableColoredProcessHandler responsibility to terminate VM
276             debugProcess.stop(willBeDestroyed && !(event.getProcessHandler() instanceof KillableColoredProcessHandler));
277
278             // wait at most 10 seconds: the problem is that debugProcess.stop() can hang if there are troubles in the debuggee
279             // if processWillTerminate() is called from AWT thread debugProcess.waitFor() will block it and the whole app will hang
280             if (!DebuggerManagerThreadImpl.isManagerThread()) {
281               if (SwingUtilities.isEventDispatchThread()) {
282                 ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
283                   @Override
284                   public void run() {
285                     ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
286                     debugProcess.waitFor(10000);
287                   }
288                 }, "Waiting For Debugger Response", false, debugProcess.getProject());
289               }
290               else {
291                 debugProcess.waitFor(10000);
292               }
293             }
294           }
295         }
296       });
297     }
298     myDispatcher.getMulticaster().sessionCreated(session);
299
300     if (debugProcess.isDetached() || debugProcess.isDetaching()) {
301       session.dispose();
302       return null;
303     }
304     if (environment.isRemote()) {
305       // optimization: that way BatchEvaluator will not try to lookup the class file in remote VM
306       // which is an expensive operation when executed first time
307       debugProcess.putUserData(BatchEvaluator.REMOTE_SESSION_KEY, Boolean.TRUE);
308     }
309
310     return session;
311   }
312
313   @Override
314   public DebugProcessImpl getDebugProcess(final ProcessHandler processHandler) {
315     synchronized (mySessions) {
316       DebuggerSession session = mySessions.get(processHandler);
317       return session != null ? session.getProcess() : null;
318     }
319   }
320
321   @SuppressWarnings("UnusedDeclaration")
322   @Nullable
323   public DebuggerSession getDebugSession(final ProcessHandler processHandler) {
324     synchronized (mySessions) {
325       return mySessions.get(processHandler);
326     }
327   }
328
329   @Override
330   public void addDebugProcessListener(final ProcessHandler processHandler, final DebugProcessListener listener) {
331     DebugProcessImpl debugProcess = getDebugProcess(processHandler);
332     if (debugProcess != null) {
333       debugProcess.addDebugProcessListener(listener);
334     }
335     else {
336       processHandler.addProcessListener(new ProcessAdapter() {
337         @Override
338         public void startNotified(ProcessEvent event) {
339           DebugProcessImpl debugProcess = getDebugProcess(processHandler);
340           if (debugProcess != null) {
341             debugProcess.addDebugProcessListener(listener);
342           }
343           processHandler.removeProcessListener(this);
344         }
345       });
346     }
347   }
348
349   @Override
350   public void removeDebugProcessListener(final ProcessHandler processHandler, final DebugProcessListener listener) {
351     DebugProcessImpl debugProcess = getDebugProcess(processHandler);
352     if (debugProcess != null) {
353       debugProcess.removeDebugProcessListener(listener);
354     }
355     else {
356       processHandler.addProcessListener(new ProcessAdapter() {
357         @Override
358         public void startNotified(ProcessEvent event) {
359           DebugProcessImpl debugProcess = getDebugProcess(processHandler);
360           if (debugProcess != null) {
361             debugProcess.removeDebugProcessListener(listener);
362           }
363           processHandler.removeProcessListener(this);
364         }
365       });
366     }
367   }
368
369   @Override
370   public boolean isDebuggerManagerThread() {
371     return DebuggerManagerThreadImpl.isManagerThread();
372   }
373
374   @Override
375   @NotNull
376   public String getComponentName() {
377     return "DebuggerManager";
378   }
379
380   @NotNull
381   @Override
382   public BreakpointManager getBreakpointManager() {
383     return myBreakpointManager;
384   }
385
386   @NotNull
387   @Override
388   public DebuggerContextImpl getContext() {
389     return getContextManager().getContext();
390   }
391
392   @NotNull
393   @Override
394   public DebuggerStateManager getContextManager() {
395     return myDebuggerStateManager;
396   }
397
398   @Override
399   public void registerPositionManagerFactory(final Function<DebugProcess, PositionManager> factory) {
400     myCustomPositionManagerFactories.add(factory);
401   }
402
403   @Override
404   public void unregisterPositionManagerFactory(final Function<DebugProcess, PositionManager> factory) {
405     myCustomPositionManagerFactories.remove(factory);
406   }
407
408   private static boolean hasWhitespace(String string) {
409     int length = string.length();
410     for (int i = 0; i < length; i++) {
411       if (Character.isWhitespace(string.charAt(i))) {
412         return true;
413       }
414     }
415     return false;
416   }
417
418   /* Remoting */
419   private static void checkTargetJPDAInstalled(JavaParameters parameters) throws ExecutionException {
420     final Sdk jdk = parameters.getJdk();
421     if (jdk == null) {
422       throw new ExecutionException(DebuggerBundle.message("error.jdk.not.specified"));
423     }
424     final JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
425     String versionString = jdk.getVersionString();
426     if (version == JavaSdkVersion.JDK_1_0 || version == JavaSdkVersion.JDK_1_1) {
427       throw new ExecutionException(DebuggerBundle.message("error.unsupported.jdk.version", versionString));
428     }
429     if (SystemInfo.isWindows && version == JavaSdkVersion.JDK_1_2) {
430       final VirtualFile homeDirectory = jdk.getHomeDirectory();
431       if (homeDirectory == null || !homeDirectory.isValid()) {
432         throw new ExecutionException(DebuggerBundle.message("error.invalid.jdk.home", versionString));
433       }
434       //noinspection HardCodedStringLiteral
435       File dllFile = new File(
436         homeDirectory.getPath().replace('/', File.separatorChar) + File.separator + "bin" + File.separator + "jdwp.dll"
437       );
438       if (!dllFile.exists()) {
439         GetJPDADialog dialog = new GetJPDADialog();
440         dialog.show();
441         throw new ExecutionException(DebuggerBundle.message("error.debug.libraries.missing"));
442       }
443     }
444   }
445
446   /**
447    * for Target JDKs versions 1.2.x - 1.3.0 the Classic VM should be used for debugging
448    */
449   private static boolean shouldForceClassicVM(Sdk jdk) {
450     if (SystemInfo.isMac) {
451       return false;
452     }
453     if (jdk == null) return false;
454
455     String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
456     if (version != null) {
457       if (version.compareTo("1.4") >= 0) {
458         return false;
459       }
460       if (version.startsWith("1.2") && SystemInfo.isWindows) {
461         return true;
462       }
463       version += ".0";
464       if (version.startsWith("1.3.0") && SystemInfo.isWindows) {
465         return true;
466       }
467       if ((version.startsWith("1.3.1_07") || version.startsWith("1.3.1_08")) && SystemInfo.isWindows) {
468         return false; // fixes bug for these JDKs that it cannot start with -classic option
469       }
470     }
471
472     return DebuggerSettings.getInstance().FORCE_CLASSIC_VM;
473   }
474
475   @SuppressWarnings({"HardCodedStringLiteral"})
476   public static RemoteConnection createDebugParameters(final JavaParameters parameters,
477                                                        final boolean debuggerInServerMode,
478                                                        int transport, final String debugPort,
479                                                        boolean checkValidity)
480     throws ExecutionException {
481     if (checkValidity) {
482       checkTargetJPDAInstalled(parameters);
483     }
484
485     final boolean useSockets = transport == DebuggerSettings.SOCKET_TRANSPORT;
486
487     String address = "";
488     if (StringUtil.isEmptyOrSpaces(debugPort)) {
489       try {
490         address = DebuggerUtils.getInstance().findAvailableDebugAddress(useSockets);
491       }
492       catch (ExecutionException e) {
493         if (checkValidity) {
494           throw e;
495         }
496       }
497     }
498     else {
499       address = debugPort;
500     }
501
502     final TransportServiceWrapper transportService = TransportServiceWrapper.getTransportService(useSockets);
503     final String debugAddress = debuggerInServerMode && useSockets ? "127.0.0.1:" + address : address;
504     String debuggeeRunProperties = "transport=" + transportService.transportId() + ",address=" + debugAddress;
505     if (debuggerInServerMode) {
506       debuggeeRunProperties += ",suspend=y,server=n";
507     }
508     else {
509       debuggeeRunProperties += ",suspend=n,server=y";
510     }
511
512     if (hasWhitespace(debuggeeRunProperties)) {
513       debuggeeRunProperties = "\"" + debuggeeRunProperties + "\"";
514     }
515     final String _debuggeeRunProperties = debuggeeRunProperties;
516
517     ApplicationManager.getApplication().runReadAction(new Runnable() {
518       @Override
519       @SuppressWarnings({"HardCodedStringLiteral"})
520       public void run() {
521         JavaSdkUtil.addRtJar(parameters.getClassPath());
522
523         final Sdk jdk = parameters.getJdk();
524         final boolean forceClassicVM = shouldForceClassicVM(jdk);
525         final boolean forceNoJIT = shouldForceNoJIT(jdk);
526         final String debugKey = System.getProperty(DEBUG_KEY_NAME, "-Xdebug");
527         final boolean needDebugKey = shouldAddXdebugKey(jdk) || !"-Xdebug".equals(debugKey) /*the key is non-standard*/;
528
529         if (forceClassicVM || forceNoJIT || needDebugKey || !isJVMTIAvailable(jdk)) {
530           parameters.getVMParametersList().replaceOrPrepend("-Xrunjdwp:", "-Xrunjdwp:" + _debuggeeRunProperties);
531         }
532         else {
533           // use newer JVMTI if available
534           parameters.getVMParametersList().replaceOrPrepend("-Xrunjdwp:", "");
535           parameters.getVMParametersList().replaceOrPrepend("-agentlib:jdwp=", "-agentlib:jdwp=" + _debuggeeRunProperties);
536         }
537
538         if (forceNoJIT) {
539           parameters.getVMParametersList().replaceOrPrepend("-Djava.compiler=", "-Djava.compiler=NONE");
540           parameters.getVMParametersList().replaceOrPrepend("-Xnoagent", "-Xnoagent");
541         }
542
543         if (needDebugKey) {
544           parameters.getVMParametersList().replaceOrPrepend(debugKey, debugKey);
545         }
546         else {
547           // deliberately skip outdated parameter because it can disable full-speed debugging for some jdk builds
548           // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6272174
549           parameters.getVMParametersList().replaceOrPrepend("-Xdebug", "");
550         }
551
552         parameters.getVMParametersList().replaceOrPrepend("-classic", forceClassicVM ? "-classic" : "");
553       }
554     });
555
556     return new RemoteConnection(useSockets, "127.0.0.1", address, debuggerInServerMode);
557   }
558
559   private static boolean shouldForceNoJIT(Sdk jdk) {
560     if (DebuggerSettings.getInstance().DISABLE_JIT) {
561       return true;
562     }
563     if (jdk != null) {
564       final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
565       if (version != null && (version.startsWith("1.2") || version.startsWith("1.3"))) {
566         return true;
567       }
568     }
569     return false;
570   }
571
572   private static boolean shouldAddXdebugKey(Sdk jdk) {
573     if (jdk == null) {
574       return true; // conservative choice
575     }
576     if (DebuggerSettings.getInstance().DISABLE_JIT) {
577       return true;
578     }
579
580     //if (ApplicationManager.getApplication().isUnitTestMode()) {
581     // need this in unit tests to avoid false alarms when comparing actual output with expected output
582     //return true;
583     //}
584
585     final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
586     return version == null ||
587            //version.startsWith("1.5") ||
588            version.startsWith("1.4") ||
589            version.startsWith("1.3") ||
590            version.startsWith("1.2") ||
591            version.startsWith("1.1") ||
592            version.startsWith("1.0");
593   }
594
595   private static boolean isJVMTIAvailable(Sdk jdk) {
596     if (jdk == null) {
597       return false; // conservative choice
598     }
599
600     final String version = JdkUtil.getJdkMainAttribute(jdk, Attributes.Name.IMPLEMENTATION_VERSION);
601     if (version == null) {
602       return false;
603     }
604     return !(version.startsWith("1.4") ||
605              version.startsWith("1.3") ||
606              version.startsWith("1.2") ||
607              version.startsWith("1.1") ||
608              version.startsWith("1.0"));
609   }
610
611   public static RemoteConnection createDebugParameters(final JavaParameters parameters,
612                                                        GenericDebuggerRunnerSettings settings,
613                                                        boolean checkValidity)
614     throws ExecutionException {
615     return createDebugParameters(parameters, settings.LOCAL, settings.getTransport(), settings.getDebugPort(), checkValidity);
616   }
617
618   private static class MyDebuggerStateManager extends DebuggerStateManager {
619     private DebuggerSession myDebuggerSession;
620
621     @NotNull
622     @Override
623     public DebuggerContextImpl getContext() {
624       return myDebuggerSession == null ? DebuggerContextImpl.EMPTY_CONTEXT : myDebuggerSession.getContextManager().getContext();
625     }
626
627     @Override
628     public void setState(@NotNull final DebuggerContextImpl context, DebuggerSession.State state, DebuggerSession.Event event, String description) {
629       ApplicationManager.getApplication().assertIsDispatchThread();
630       myDebuggerSession = context.getDebuggerSession();
631       if (myDebuggerSession != null) {
632         myDebuggerSession.getContextManager().setState(context, state, event, description);
633       }
634       else {
635         fireStateChanged(context, event);
636       }
637     }
638   }
639
640   private void dispose(DebuggerSession session) {
641     ProcessHandler processHandler = session.getProcess().getProcessHandler();
642     synchronized (mySessions) {
643       DebuggerSession removed = mySessions.remove(processHandler);
644       LOG.assertTrue(removed != null);
645       myDispatcher.getMulticaster().sessionRemoved(session);
646     }
647   }
648 }