b4765e42e86e76a73c809717ff8c427968bedb1f
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / XDebugSessionImpl.java
1 /*
2  * Copyright 2000-2009 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.xdebugger.impl;
17
18 import com.intellij.execution.configurations.RunProfile;
19 import com.intellij.execution.filters.HyperlinkInfo;
20 import com.intellij.execution.filters.OpenFileHyperlinkInfo;
21 import com.intellij.execution.process.ProcessHandler;
22 import com.intellij.execution.runners.ExecutionEnvironment;
23 import com.intellij.execution.runners.ProgramRunner;
24 import com.intellij.execution.ui.ConsoleView;
25 import com.intellij.execution.ui.ConsoleViewContentType;
26 import com.intellij.execution.ui.RunContentDescriptor;
27 import com.intellij.openapi.application.ReadAction;
28 import com.intellij.openapi.application.Result;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.MessageType;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.wm.ToolWindowId;
35 import com.intellij.openapi.wm.ToolWindowManager;
36 import com.intellij.util.EventDispatcher;
37 import com.intellij.util.ui.UIUtil;
38 import com.intellij.xdebugger.*;
39 import com.intellij.xdebugger.breakpoints.*;
40 import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
41 import com.intellij.xdebugger.frame.XExecutionStack;
42 import com.intellij.xdebugger.frame.XStackFrame;
43 import com.intellij.xdebugger.frame.XSuspendContext;
44 import com.intellij.xdebugger.impl.breakpoints.*;
45 import com.intellij.xdebugger.impl.evaluate.quick.common.ValueLookupManager;
46 import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
47 import com.intellij.xdebugger.impl.ui.XDebugSessionData;
48 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
49 import com.intellij.xdebugger.stepping.XSmartStepIntoHandler;
50 import com.intellij.xdebugger.stepping.XSmartStepIntoVariant;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53
54 import javax.swing.*;
55 import java.util.*;
56
57 /**
58  * @author nik
59  */
60 public class XDebugSessionImpl implements XDebugSession {
61   private static final Logger LOG = Logger.getInstance("#com.intellij.xdebugger.impl.XDebugSessionImpl");
62   private XDebugProcess myDebugProcess;
63   private final Map<XBreakpoint<?>, CustomizedBreakpointPresentation> myRegisteredBreakpoints = new HashMap<XBreakpoint<?>, CustomizedBreakpointPresentation>();
64   private final Set<XBreakpoint<?>> myDisabledSlaveBreakpoints = new HashSet<XBreakpoint<?>>();
65   private boolean myBreakpointsMuted;
66   private boolean myBreakpointsDisabled;
67   private final XDebuggerManagerImpl myDebuggerManager;
68   private MyBreakpointListener myBreakpointListener;
69   private XSuspendContext mySuspendContext;
70   private XStackFrame myCurrentStackFrame;
71   private XSourcePosition myCurrentPosition;
72   private boolean myPaused;
73   private MyDependentBreakpointListener myDependentBreakpointListener;
74   private String mySessionName;
75   private XDebugSessionTab mySessionTab;
76   private final EventDispatcher<XDebugSessionListener> myDispatcher = EventDispatcher.create(XDebugSessionListener.class);
77   private Project myProject;
78   private @Nullable ExecutionEnvironment myEnvironment;
79   private ProgramRunner myRunner;
80   private boolean myStopped;
81   private boolean myPauseActionSupported;
82
83   public XDebugSessionImpl(final @NotNull ExecutionEnvironment env, final @NotNull ProgramRunner runner, XDebuggerManagerImpl debuggerManager) {
84     this(env, runner, debuggerManager, env.getRunProfile().getName());
85   }
86
87   public XDebugSessionImpl(final @Nullable ExecutionEnvironment env, final @Nullable ProgramRunner runner, XDebuggerManagerImpl debuggerManager,
88                            final @NotNull String sessionName) {
89     myEnvironment = env;
90     myRunner = runner;
91     mySessionName = sessionName;
92     myDebuggerManager = debuggerManager;
93     myProject = debuggerManager.getProject();
94     ValueLookupManager.getInstance(myProject).startListening();
95   }
96
97   @NotNull
98   public String getSessionName() {
99     return mySessionName;
100   }
101
102   @NotNull
103   public RunContentDescriptor getRunContentDescriptor() {
104     LOG.assertTrue(mySessionTab != null, "Call init() first!");
105     return mySessionTab.getRunContentDescriptor();
106   }
107
108   public void setPauseActionSupported(final boolean isSupported) {
109     myPauseActionSupported = isSupported;
110   }
111
112   public void rebuildViews() {
113     mySessionTab.rebuildViews();
114   }
115
116   @Nullable
117   public RunProfile getRunProfile() {
118     return myEnvironment != null ? myEnvironment.getRunProfile() : null;
119   }
120
121   public boolean isPauseActionSupported() {
122     return myPauseActionSupported;
123   }
124
125   @NotNull
126   public Project getProject() {
127     return myDebuggerManager.getProject();
128   }
129
130   @NotNull
131   public XDebugProcess getDebugProcess() {
132     return myDebugProcess;
133   }
134
135   public boolean isSuspended() {
136     return myPaused && mySuspendContext != null;
137   }
138
139   public boolean isPaused() {
140     return myPaused;
141   }
142
143   @Nullable
144   public XStackFrame getCurrentStackFrame() {
145     return myCurrentStackFrame;
146   }
147
148   public XSuspendContext getSuspendContext() {
149     return mySuspendContext;
150   }
151
152   @Nullable
153   public XSourcePosition getCurrentPosition() {
154     return myCurrentPosition;
155   }
156
157   public XDebugSessionTab init(final XDebugProcess process, @NotNull final XDebugSessionData sessionData) {
158     LOG.assertTrue(myDebugProcess == null);
159     myDebugProcess = process;
160
161     XBreakpointManagerImpl breakpointManager = myDebuggerManager.getBreakpointManager();
162     XDependentBreakpointManager dependentBreakpointManager = breakpointManager.getDependentBreakpointManager();
163     disableSlaveBreakpoints(dependentBreakpointManager);
164     processAllBreakpoints(true, false);
165
166     myBreakpointListener = new MyBreakpointListener();
167     breakpointManager.addBreakpointListener(myBreakpointListener);
168     myDependentBreakpointListener = new MyDependentBreakpointListener();
169     dependentBreakpointManager.addListener(myDependentBreakpointListener);
170
171     initSessionTab(sessionData);
172     process.sessionInitialized();
173
174     return mySessionTab;
175   }
176
177   public XDebugSessionTab getSessionTab() {
178     return mySessionTab;
179   }
180
181   private void initSessionTab(@NotNull XDebugSessionData sessionData) {
182     mySessionTab = new XDebugSessionTab(myProject, mySessionName);
183     if (myEnvironment != null) {
184       mySessionTab.setEnvironment(myEnvironment);
185     }
186     Disposer.register(myProject, mySessionTab);
187     mySessionTab.attachToSession(this, myRunner, myEnvironment, sessionData);
188   }
189
190   private void disableSlaveBreakpoints(final XDependentBreakpointManager dependentBreakpointManager) {
191     Set<XBreakpoint<?>> slaveBreakpoints = dependentBreakpointManager.getAllSlaveBreakpoints();
192     Set<XBreakpointType<?,?>> breakpointTypes = new HashSet<XBreakpointType<?,?>>();
193     for (XBreakpointHandler<?> handler : myDebugProcess.getBreakpointHandlers()) {
194       breakpointTypes.add(getBreakpointTypeClass(handler));
195     }
196     for (XBreakpoint<?> slaveBreakpoint : slaveBreakpoints) {
197       if (breakpointTypes.contains(slaveBreakpoint.getType())) {
198         myDisabledSlaveBreakpoints.add(slaveBreakpoint);
199       }
200     }
201   }
202
203   private static <B extends XBreakpoint<?>> XBreakpointType<?, ?> getBreakpointTypeClass(final XBreakpointHandler<B> handler) {
204     return XDebuggerUtil.getInstance().findBreakpointType(handler.getBreakpointTypeClass());
205   }
206
207   private <B extends XBreakpoint<?>> void processBreakpoints(final XBreakpointHandler<B> handler, boolean register, final boolean temporary) {
208     XBreakpointType<B,?> type = XDebuggerUtil.getInstance().findBreakpointType(handler.getBreakpointTypeClass());
209     Collection<? extends B> breakpoints = myDebuggerManager.getBreakpointManager().getBreakpoints(type);
210     for (B b : breakpoints) {
211       handleBreakpoint(handler, b, register, temporary);
212     }
213   }
214
215   private <B extends XBreakpoint<?>> void handleBreakpoint(final XBreakpointHandler<B> handler, final B b, final boolean register,
216                                                            final boolean temporary) {
217     if (register && isBreakpointActive(b)) {
218       synchronized (myRegisteredBreakpoints) {
219         myRegisteredBreakpoints.put(b, new CustomizedBreakpointPresentation());
220       }
221       handler.registerBreakpoint(b);
222     }
223     if (!register) {
224       boolean removed = false;
225       synchronized (myRegisteredBreakpoints) {
226         if (myRegisteredBreakpoints.containsKey(b)) {
227           myRegisteredBreakpoints.remove(b);
228           removed = true;
229         }
230       }
231       if (removed) {
232         handler.unregisterBreakpoint(b, temporary);
233       }
234     }
235   }
236
237   @Nullable
238   public CustomizedBreakpointPresentation getBreakpointPresentation(@NotNull XLineBreakpoint<?> breakpoint) {
239     synchronized (myRegisteredBreakpoints) {
240       return myRegisteredBreakpoints.get(breakpoint);
241     }
242   }
243
244   private void processAllHandlers(final XBreakpoint<?> breakpoint, final boolean register) {
245     for (XBreakpointHandler<?> handler : myDebugProcess.getBreakpointHandlers()) {
246       processBreakpoint(breakpoint, handler, register);
247     }
248   }
249
250   private <B extends XBreakpoint<?>> void processBreakpoint(final XBreakpoint<?> breakpoint, final XBreakpointHandler<B> handler, boolean register) {
251     XBreakpointType<?, ?> type = breakpoint.getType();
252     if (handler.getBreakpointTypeClass().equals(type.getClass())) {
253       //noinspection unchecked
254       B b = (B)breakpoint;
255       handleBreakpoint(handler, b, register, false);
256     }
257   }
258
259   private boolean isBreakpointActive(final XBreakpoint<?> b) {
260     return !myBreakpointsMuted && b.isEnabled() && !myDisabledSlaveBreakpoints.contains(b);
261   }
262
263   public boolean areBreakpointsMuted() {
264     return myBreakpointsMuted;
265   }
266
267   public void addSessionListener(@NotNull final XDebugSessionListener listener) {
268     myDispatcher.addListener(listener);
269   }
270
271   public void removeSessionListener(@NotNull final XDebugSessionListener listener) {
272     myDispatcher.removeListener(listener);
273   }
274
275   public void setBreakpointMuted(boolean muted) {
276     if (myBreakpointsMuted == muted) return;
277     myBreakpointsMuted = muted;
278     processAllBreakpoints(!muted, muted);
279     myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueAllBreakpointsUpdate();
280   }
281
282   public void stepOver(final boolean ignoreBreakpoints) {
283     if (ignoreBreakpoints) {
284       disableBreakpoints();
285     }
286     doResume();
287     myDebugProcess.startStepOver();
288   }
289
290   public void stepInto() {
291     doResume();
292     myDebugProcess.startStepInto();
293   }
294
295   public void stepOut() {
296     doResume();
297     myDebugProcess.startStepOut();
298   }
299
300   public <V extends XSmartStepIntoVariant> void smartStepInto(XSmartStepIntoHandler<V> handler, V variant) {
301     doResume();
302     handler.startStepInto(variant);
303   }
304
305   public void forceStepInto() {
306     stepInto();
307   }
308
309   public void runToPosition(@NotNull final XSourcePosition position, final boolean ignoreBreakpoints) {
310     if (ignoreBreakpoints) {
311       disableBreakpoints();
312     }
313     doResume();
314     myDebugProcess.runToPosition(position);
315   }
316
317   public void pause() {
318     myDebugProcess.startPausing();
319   }
320
321   private void processAllBreakpoints(final boolean register, final boolean temporary) {
322     for (XBreakpointHandler<?> handler : myDebugProcess.getBreakpointHandlers()) {
323       processBreakpoints(handler, register, temporary);
324     }
325   }
326
327   private void disableBreakpoints() {
328     myBreakpointsDisabled = true;
329     processAllBreakpoints(false, true);
330   }
331
332   public void resume() {
333     doResume();
334     myDebugProcess.resume();
335   }
336
337   private void doResume() {
338     myDispatcher.getMulticaster().beforeSessionResume();
339     myDebuggerManager.setActiveSession(this, null, false);
340     mySuspendContext = null;
341     myCurrentStackFrame = null;
342     myCurrentPosition = null;
343     myPaused = false;
344     myDispatcher.getMulticaster().sessionResumed();
345   }
346
347   public void showExecutionPoint() {
348     if (mySuspendContext != null) {
349       XExecutionStack executionStack = mySuspendContext.getActiveExecutionStack();
350       if (executionStack != null) {
351         XStackFrame topFrame = executionStack.getTopFrame();
352         if (topFrame != null) {
353           setCurrentStackFrame(topFrame);
354           myDebuggerManager.showExecutionPosition();
355         }
356       }
357     }
358   }
359
360   public void setCurrentStackFrame(@NotNull final XStackFrame frame) {
361     if (mySuspendContext == null) return;
362
363     boolean frameChanged = myCurrentStackFrame != frame;
364     myCurrentStackFrame = frame;
365     activateSession();
366
367     if (frameChanged) {
368       myDispatcher.getMulticaster().stackFrameChanged();
369     }
370   }
371
372   public void activateSession() {
373     XSourcePosition position = myCurrentStackFrame != null ? myCurrentStackFrame.getSourcePosition() : null;
374     if (position != null) {
375       XExecutionStack activeExecutionStack = mySuspendContext.getActiveExecutionStack();
376       boolean isTopFrame = activeExecutionStack != null && activeExecutionStack.getTopFrame() == myCurrentStackFrame;
377       myDebuggerManager.setActiveSession(this, position, !isTopFrame);
378     }
379     else {
380       myDebuggerManager.setActiveSession(this, null, false);
381     }
382   }
383
384   public void updateBreakpointPresentation(@NotNull final XLineBreakpoint<?> breakpoint, @Nullable final Icon icon, @Nullable final String errorMessage) {
385     CustomizedBreakpointPresentation presentation;
386     synchronized (myRegisteredBreakpoints) {
387       presentation = myRegisteredBreakpoints.get(breakpoint);
388       if (presentation != null) {
389         presentation.setErrorMessage(errorMessage);
390         presentation.setIcon(icon);
391       }
392     }
393     if (presentation != null) {
394       myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueBreakpointUpdate((XLineBreakpointImpl<?>)breakpoint);
395     }
396   }
397
398   public boolean breakpointReached(@NotNull final XBreakpoint<?> breakpoint, @NotNull final XSuspendContext suspendContext) {
399     return breakpointReached(breakpoint, null, suspendContext);
400   }
401
402   public boolean breakpointReached(@NotNull XBreakpoint<?> breakpoint, @Nullable String evaluatedLogExpression,
403                                    @NotNull XSuspendContext suspendContext) {
404     XDebuggerEvaluator evaluator = XDebuggerUtilImpl.getEvaluator(suspendContext);
405     String condition = breakpoint.getCondition();
406     if (condition != null && evaluator != null) {
407       LOG.debug("evaluating condition: " + condition);
408       boolean result = evaluator.evaluateCondition(condition);        
409       LOG.debug("condition evaluates to " + result);                 
410       if (!result) {
411         return false;
412       }
413     }
414
415     if (breakpoint.isLogMessage()) {
416       String text = StringUtil.decapitalize(XBreakpointUtil.getDisplayText(breakpoint));
417       final XSourcePosition position = breakpoint.getSourcePosition();
418       final OpenFileHyperlinkInfo hyperlinkInfo = position != null ? new OpenFileHyperlinkInfo(myProject, position.getFile(), position.getLine()) : null;
419       printMessage(XDebuggerBundle.message("xbreakpoint.reached.text") + " ", text, hyperlinkInfo);
420     }
421
422     if (evaluatedLogExpression != null) {
423       printMessage(evaluatedLogExpression, null, null);
424     }
425     else {
426       String expression = breakpoint.getLogExpression();
427       if (expression != null && evaluator != null) {
428         LOG.debug("evaluating log expression: " + expression);
429         final String message = evaluator.evaluateMessage(expression);
430         if (message != null) {
431           printMessage(message, null, null);
432         }
433       }
434     }
435
436     processDependencies(breakpoint);
437
438     if (breakpoint.getSuspendPolicy() == SuspendPolicy.NONE) {
439       return false;
440     }
441
442     positionReached(suspendContext);
443     return true;
444   }
445
446   private void processDependencies(final XBreakpoint<?> breakpoint) {
447     XDependentBreakpointManager dependentBreakpointManager = myDebuggerManager.getBreakpointManager().getDependentBreakpointManager();
448     if (!dependentBreakpointManager.isMasterOrSlave(breakpoint)) return;
449
450     List<XBreakpoint<?>> breakpoints = dependentBreakpointManager.getSlaveBreakpoints(breakpoint);
451     myDisabledSlaveBreakpoints.removeAll(breakpoints);
452     for (XBreakpoint<?> slaveBreakpoint : breakpoints) {
453       processAllHandlers(slaveBreakpoint, true);
454     }
455
456     if (dependentBreakpointManager.getMasterBreakpoint(breakpoint) != null && !dependentBreakpointManager.isLeaveEnabled(breakpoint)) {
457       boolean added = myDisabledSlaveBreakpoints.add(breakpoint);
458       if (added) {
459         processAllHandlers(breakpoint, false);
460         myDebuggerManager.getBreakpointManager().getLineBreakpointManager().queueBreakpointUpdate(breakpoint);
461       }
462     }
463   }
464
465   private void printMessage(final String message, final String hyperLinkText, @Nullable final HyperlinkInfo info) {
466     DebuggerUIUtil.invokeOnEventDispatch(new Runnable() {
467       public void run() {
468         final ConsoleView consoleView = getConsoleView();
469         consoleView.print(message, ConsoleViewContentType.SYSTEM_OUTPUT);
470         if (info != null) {
471           consoleView.printHyperlink(hyperLinkText, info);
472         }
473         else if (hyperLinkText != null) {
474           consoleView.print(hyperLinkText, ConsoleViewContentType.SYSTEM_OUTPUT);
475         }
476         consoleView.print("\n", ConsoleViewContentType.SYSTEM_OUTPUT);
477       }
478     });
479   }
480
481   private ConsoleView getConsoleView() {
482     return (ConsoleView)mySessionTab.getConsole();
483   }
484
485   public void positionReached(@NotNull final XSuspendContext suspendContext) {
486     enableBreakpoints();
487     mySuspendContext = suspendContext;
488     XExecutionStack executionStack = suspendContext.getActiveExecutionStack();
489     myCurrentStackFrame = executionStack != null ? executionStack.getTopFrame() : null;
490     myCurrentPosition = myCurrentStackFrame != null ? myCurrentStackFrame.getSourcePosition() : null;
491
492     myPaused = true;
493     if (myCurrentPosition != null) {
494       myDebuggerManager.setActiveSession(this, myCurrentPosition, false);
495     }
496     UIUtil.invokeLaterIfNeeded(new Runnable() {
497       public void run() {
498         mySessionTab.toFront();
499       }
500     });
501     myDispatcher.getMulticaster().sessionPaused();
502   }
503
504   private void enableBreakpoints() {
505     if (myBreakpointsDisabled) {
506       myBreakpointsDisabled = false;
507       new ReadAction() {
508         protected void run(final Result result) {
509           processAllBreakpoints(true, false);
510         }
511       }.execute();
512     }
513   }
514
515   public boolean isStopped() {
516     return myStopped;
517   }
518
519   public void stopImpl() {
520     if (myStopped) return;
521
522     myDebugProcess.stop();
523     myCurrentPosition = null;
524     myCurrentStackFrame = null;
525     mySuspendContext = null;
526     myDebuggerManager.setActiveSession(this, null, false);
527     XBreakpointManagerImpl breakpointManager = myDebuggerManager.getBreakpointManager();
528     breakpointManager.removeBreakpointListener(myBreakpointListener);
529     breakpointManager.getDependentBreakpointManager().removeListener(myDependentBreakpointListener);
530     myStopped = true;
531     myDebuggerManager.removeSession(this);
532     myDispatcher.getMulticaster().sessionStopped();
533   }
534
535   public boolean isDisabledSlaveBreakpoint(final XBreakpoint<?> breakpoint) {
536     return myDisabledSlaveBreakpoints.contains(breakpoint);
537   }
538
539   public void stop() {
540     ProcessHandler processHandler = myDebugProcess.getProcessHandler();
541     if (processHandler.isProcessTerminated() || processHandler.isProcessTerminating()) return; 
542
543     if (processHandler.detachIsDefault()) {
544       processHandler.detachProcess();
545     }
546     else {
547       processHandler.destroyProcess();
548     }
549   }
550
551   @Override
552   public void reportError(final String message) {
553     UIUtil.invokeLaterIfNeeded(new Runnable() {
554       @Override
555       public void run() {
556         ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.DEBUG, MessageType.ERROR, message, null, null);
557       }
558     });
559   }
560
561   private class MyBreakpointListener implements XBreakpointListener<XBreakpoint<?>> {
562     public void breakpointAdded(@NotNull final XBreakpoint<?> breakpoint) {
563       if (!myBreakpointsDisabled) {
564         processAllHandlers(breakpoint, true);
565       }
566     }
567
568     public void breakpointRemoved(@NotNull final XBreakpoint<?> breakpoint) {
569       processAllHandlers(breakpoint, false);
570     }
571
572     public void breakpointChanged(@NotNull final XBreakpoint<?> breakpoint) {
573       breakpointRemoved(breakpoint);
574       breakpointAdded(breakpoint);
575     }
576   }
577
578   private class MyDependentBreakpointListener implements XDependentBreakpointListener {
579     public void dependencySet(final XBreakpoint<?> slave, final XBreakpoint<?> master) {
580       boolean added = myDisabledSlaveBreakpoints.add(slave);
581       if (added) {
582         processAllHandlers(slave, false);
583       }
584     }
585
586     public void dependencyCleared(final XBreakpoint<?> breakpoint) {
587       boolean removed = myDisabledSlaveBreakpoints.remove(breakpoint);
588       if (removed) {
589         processAllHandlers(breakpoint, true);
590       }
591     }
592   }
593 }