cleanup
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / breakpoints / Breakpoint.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
17 /*
18  * Class Breakpoint
19  * @author Jeka
20  */
21 package com.intellij.debugger.ui.breakpoints;
22
23 import com.intellij.debugger.*;
24 import com.intellij.debugger.engine.*;
25 import com.intellij.debugger.engine.evaluation.*;
26 import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl;
27 import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
28 import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
29 import com.intellij.debugger.impl.DebuggerUtilsEx;
30 import com.intellij.debugger.jdi.StackFrameProxyImpl;
31 import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
32 import com.intellij.debugger.requests.ClassPrepareRequestor;
33 import com.intellij.debugger.settings.DebuggerSettings;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.util.Computable;
37 import com.intellij.openapi.util.InvalidDataException;
38 import com.intellij.openapi.util.JDOMExternalizerUtil;
39 import com.intellij.openapi.util.Key;
40 import com.intellij.psi.PsiClass;
41 import com.intellij.psi.PsiElement;
42 import com.intellij.ui.AppUIUtil;
43 import com.intellij.ui.classFilter.ClassFilter;
44 import com.intellij.util.StringBuilderSpinAllocator;
45 import com.intellij.util.ThreeState;
46 import com.intellij.xdebugger.XExpression;
47 import com.intellij.xdebugger.breakpoints.SuspendPolicy;
48 import com.intellij.xdebugger.breakpoints.XBreakpoint;
49 import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
50 import com.intellij.xdebugger.impl.XDebugSessionImpl;
51 import com.intellij.xdebugger.impl.XDebuggerHistoryManager;
52 import com.intellij.xdebugger.impl.XDebuggerUtilImpl;
53 import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
54 import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
55 import com.intellij.xdebugger.impl.breakpoints.ui.XBreakpointActionsPanel;
56 import com.sun.jdi.*;
57 import com.sun.jdi.event.LocatableEvent;
58 import org.jdom.Element;
59 import org.jetbrains.annotations.NonNls;
60 import org.jetbrains.annotations.NotNull;
61 import org.jetbrains.annotations.Nullable;
62 import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
63
64 import javax.swing.*;
65 import java.util.Arrays;
66
67 public abstract class Breakpoint<P extends JavaBreakpointProperties> implements FilteredRequestor, ClassPrepareRequestor {
68   public static final Key<Breakpoint> DATA_KEY = Key.create("JavaBreakpoint");
69
70   final XBreakpoint<P> myXBreakpoint;
71   protected final Project myProject;
72
73   @NonNls private static final String LOG_MESSAGE_OPTION_NAME = "LOG_MESSAGE";
74   public static final Breakpoint[] EMPTY_ARRAY = new Breakpoint[0];
75   protected boolean myCachedVerifiedState = false;
76
77   protected Breakpoint(@NotNull Project project, XBreakpoint<P> xBreakpoint) {
78     myProject = project;
79     myXBreakpoint = xBreakpoint;
80   }
81
82   @NotNull
83   public Project getProject() {
84     return myProject;
85   }
86
87   @NotNull
88   protected P getProperties() {
89     return myXBreakpoint.getProperties();
90   }
91
92   public XBreakpoint<P> getXBreakpoint() {
93     return myXBreakpoint;
94   }
95
96   @Nullable
97   public abstract PsiClass getPsiClass();
98   /**
99    * Request for creating all needed JPDA requests in the specified VM
100    * @param debuggerProcess the requesting process
101    */
102   public abstract void createRequest(DebugProcessImpl debugProcess);
103
104   protected boolean shouldCreateRequest(final DebugProcessImpl debugProcess) {
105     return ApplicationManager.getApplication().runReadAction((Computable<Boolean>)() -> {
106       JavaDebugProcess process = debugProcess.getXdebugProcess();
107       return process != null
108              && debugProcess.isAttached()
109              && ((XDebugSessionImpl)process.getSession()).isBreakpointActive(myXBreakpoint)
110              && debugProcess.getRequestsManager().findRequests(this).isEmpty();
111     });
112   }
113
114   /**
115    * Request for creating all needed JPDA requests in the specified VM
116    * @param debuggerProcess the requesting process
117    */
118   @Override
119   public abstract void processClassPrepare(DebugProcess debuggerProcess, final ReferenceType referenceType);
120
121   public abstract String getDisplayName ();
122   
123   public String getShortName() {
124     return getDisplayName();
125   }
126
127   @Nullable
128   public String getClassName() {
129     return null;
130   }
131
132   public void markVerified(boolean isVerified) {
133     myCachedVerifiedState = isVerified;
134   }
135
136   public boolean isRemoveAfterHit() {
137     return myXBreakpoint instanceof XLineBreakpoint && ((XLineBreakpoint)myXBreakpoint).isTemporary();
138   }
139
140   public void setRemoveAfterHit(boolean value) {
141     if (myXBreakpoint instanceof XLineBreakpoint) {
142       ((XLineBreakpoint)myXBreakpoint).setTemporary(value);
143     }
144   }
145
146   @Nullable
147   public String getShortClassName() {
148     final String className = getClassName();
149     if (className == null) {
150       return null;
151     }
152
153     final int dotIndex = className.lastIndexOf('.');
154     return dotIndex >= 0 && dotIndex + 1 < className.length() ? className.substring(dotIndex + 1) : className;
155   }
156
157   @Nullable
158   public String getPackageName() {
159     return null;
160   }
161
162   public abstract Icon getIcon();
163
164   public abstract void reload();
165
166   /**
167    * returns UI representation
168    */
169   public abstract String getEventMessage(LocatableEvent event);
170
171   public abstract boolean isValid();
172
173   public abstract Key<? extends Breakpoint> getCategory();
174
175   /**
176    * Associates breakpoint with class.
177    *    Create requests for loaded class and registers callback for loading classes
178    * @param debugProcess the requesting process
179    */
180   protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) {
181     debugProcess.getRequestsManager().callbackOnPrepareClasses(this, classToBeLoaded);
182
183     debugProcess.getVirtualMachineProxy().classesByName(classToBeLoaded).stream()
184       .filter(ReferenceType::isPrepared)
185       .forEach(aList -> processClassPrepare(debugProcess, aList));
186   }
187
188   protected void createOrWaitPrepare(final DebugProcessImpl debugProcess, @NotNull final SourcePosition classPosition) {
189     debugProcess.getRequestsManager().callbackOnPrepareClasses(this, classPosition);
190
191     debugProcess.getPositionManager().getAllClasses(classPosition).stream()
192       .filter(ReferenceType::isPrepared)
193       .forEach(refType -> processClassPrepare(debugProcess, refType));
194   }
195
196   protected ObjectReference getThisObject(SuspendContextImpl context, LocatableEvent event) throws EvaluateException {
197     ThreadReferenceProxyImpl thread = context.getThread();
198     if(thread != null) {
199       StackFrameProxyImpl stackFrameProxy = thread.frame(0);
200       if(stackFrameProxy != null) {
201         return stackFrameProxy.thisObject();
202       }
203     }
204     return null;
205   }
206
207   @Override
208   public boolean processLocatableEvent(final SuspendContextCommandImpl action, final LocatableEvent event) throws EventProcessingException {
209     final SuspendContextImpl context = action.getSuspendContext();
210     if(!isValid()) {
211       context.getDebugProcess().getRequestsManager().deleteRequest(this);
212       return false;
213     }
214
215     final String[] title = {DebuggerBundle.message("title.error.evaluating.breakpoint.condition") };
216
217     try {
218       final StackFrameProxyImpl frameProxy = context.getThread().frame(0);
219       if (frameProxy == null) {
220         // might be if the thread has been collected
221         return false;
222       }
223
224       final EvaluationContextImpl evaluationContext = new EvaluationContextImpl(
225         action.getSuspendContext(),
226         frameProxy,
227         getThisObject(context, event)
228       );
229
230       if(!evaluateCondition(evaluationContext, event)) {
231         return false;
232       }
233
234       title[0] = DebuggerBundle.message("title.error.evaluating.breakpoint.action");
235       runAction(evaluationContext, event);
236     }
237     catch (final EvaluateException ex) {
238       if(ApplicationManager.getApplication().isUnitTestMode()) {
239         System.out.println(ex.getMessage());
240         return false;
241       }
242
243       throw new EventProcessingException(title[0], ex.getMessage(), ex);
244     } 
245
246     return true;
247   }
248
249   private void runAction(final EvaluationContextImpl context, LocatableEvent event) {
250     final DebugProcessImpl debugProcess = context.getDebugProcess();
251     if (isLogEnabled() || isLogExpressionEnabled()) {
252       final StringBuilder buf = StringBuilderSpinAllocator.alloc();
253       try {
254         if (myXBreakpoint.isLogMessage()) {
255           buf.append(getEventMessage(event));
256           buf.append("\n");
257         }
258         if (isLogExpressionEnabled()) {
259           if (!debugProcess.isAttached()) {
260             return;
261           }
262
263           final TextWithImports expressionToEvaluate = getLogMessage();
264           try {
265             SourcePosition position = ContextUtil.getSourcePosition(context);
266             PsiElement element = ContextUtil.getContextElement(context, position);
267             ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject,
268               () -> EvaluatorBuilderImpl.build(expressionToEvaluate, element, position, myProject));
269             Value eval = evaluator.evaluate(context);
270             buf.append(eval instanceof VoidValue ? "void" : DebuggerUtils.getValueAsString(context, eval));
271           }
272           catch (EvaluateException e) {
273             buf.append(DebuggerBundle.message("error.unable.to.evaluate.expression"));
274             buf.append(" \"");
275             buf.append(expressionToEvaluate);
276             buf.append("\"");
277             buf.append(" : ");
278             buf.append(e.getMessage());
279           }
280           buf.append("\n");
281         }
282         if (buf.length() > 0) {
283           debugProcess.printToConsole(buf.toString());
284         }
285       }
286       finally {
287         StringBuilderSpinAllocator.dispose(buf);
288       }
289     }
290     if (isRemoveAfterHit()) {
291       handleTemporaryBreakpointHit(debugProcess);
292     }
293   }
294
295   /**
296    * @return true if the ID was added or false otherwise
297    */
298   private boolean hasObjectID(long id) {
299     return Arrays.stream(getInstanceFilters()).anyMatch(instanceFilter -> instanceFilter.getId() == id);
300   }
301
302   public boolean evaluateCondition(final EvaluationContextImpl context, LocatableEvent event) throws EvaluateException {
303     final DebugProcessImpl debugProcess = context.getDebugProcess();
304     if (isCountFilterEnabled()) {
305       debugProcess.getVirtualMachineProxy().suspend();
306       debugProcess.getRequestsManager().deleteRequest(this);
307       createRequest(debugProcess);
308       debugProcess.getVirtualMachineProxy().resume();
309     }
310     if (isInstanceFiltersEnabled()) {
311       Value value = context.getThisObject();
312       if (value != null) {  // non-static
313         ObjectReference reference = (ObjectReference)value;
314         if (!hasObjectID(reference.uniqueID())) {
315           return false;
316         }
317       }
318     }
319
320     if (isClassFiltersEnabled() &&
321         !typeMatchesClassFilters(calculateEventClass(context, event), getClassFilters(), getClassExclusionFilters())) {
322       return false;
323     }
324
325     if (!isConditionEnabled() || getCondition().getText().isEmpty()) {
326       return true;
327     }
328
329     StackFrameProxyImpl frame = context.getFrameProxy();
330     if (frame != null) {
331       Location location = frame.location();
332       if (location != null) {
333         ThreeState result = debugProcess.getPositionManager().evaluateCondition(context, frame, location, getCondition().getText());
334         if (result != ThreeState.UNSURE) {
335           return result == ThreeState.YES;
336         }
337       }
338     }
339
340     try {
341       Project project = context.getProject();
342       SourcePosition contextSourcePosition = ContextUtil.getSourcePosition(context);
343       ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(project, () -> {
344         // IMPORTANT: calculate context psi element basing on the location where the exception
345         // has been hit, not on the location where it was set. (For line breakpoints these locations are the same, however,
346         // for method, exception and field breakpoints these locations differ)
347         PsiElement contextPsiElement = ContextUtil.getContextElement(contextSourcePosition);
348         if (contextPsiElement == null) {
349           contextPsiElement = getEvaluationElement(); // as a last resort
350         }
351         return EvaluatorBuilderImpl.build(getCondition(), contextPsiElement, contextSourcePosition, project);
352       });
353       return DebuggerUtilsEx.evaluateBoolean(evaluator, context);
354     }
355     catch (EvaluateException ex) {
356       if (ex.getCause() instanceof VMDisconnectedException) {
357         return false;
358       }
359       throw EvaluateExceptionUtil.createEvaluateException(
360         DebuggerBundle.message("error.failed.evaluating.breakpoint.condition", getCondition(), ex.getMessage())
361       );
362     }
363   }
364
365   protected String calculateEventClass(EvaluationContextImpl context, LocatableEvent event) throws EvaluateException {
366     return event.location().declaringType().name();
367   }
368
369   protected static boolean typeMatchesClassFilters(@Nullable String typeName, ClassFilter[] includeFilters, ClassFilter[] exludeFilters) {
370     if (typeName == null) {
371       return true;
372     }
373     boolean matches = false, hasEnabled = false;
374     for (ClassFilter classFilter : includeFilters) {
375       if (classFilter.isEnabled()) {
376         hasEnabled = true;
377         if (classFilter.matches(typeName)) {
378           matches = true;
379           break;
380         }
381       }
382     }
383     if (hasEnabled && !matches) {
384       return false;
385     }
386     return Arrays.stream(exludeFilters).noneMatch(classFilter -> classFilter.isEnabled() && classFilter.matches(typeName));
387   }
388
389   private void handleTemporaryBreakpointHit(final DebugProcessImpl debugProcess) {
390     // need to delete the request immediately, see IDEA-133978
391     debugProcess.getRequestsManager().deleteRequest(this);
392
393     debugProcess.addDebugProcessListener(new DebugProcessListener() {
394       @Override
395       public void resumed(SuspendContext suspendContext) {
396         removeBreakpoint();
397       }
398
399       @Override
400       public void processDetached(DebugProcess process, boolean closedByUser) {
401         removeBreakpoint();
402       }
403
404       private void removeBreakpoint() {
405         AppUIUtil.invokeOnEdt(() -> DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().removeBreakpoint(Breakpoint.this));
406         debugProcess.removeDebugProcessListener(this);
407       }
408     });
409   }
410
411   public void updateUI() {
412   }
413
414   public void readExternal(Element parentNode) throws InvalidDataException {
415     FilteredRequestorImpl requestor = new FilteredRequestorImpl(myProject);
416     requestor.readTo(parentNode, this);
417     try {
418       setEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "ENABLED")));
419     }
420     catch (Exception ignored) {
421     }
422     try {
423       setLogEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "LOG_ENABLED")));
424     }
425     catch (Exception ignored) {
426     }
427     try {
428       String logMessage = JDOMExternalizerUtil.readField(parentNode, LOG_MESSAGE_OPTION_NAME);
429       if (logMessage != null && !logMessage.isEmpty()) {
430         XExpressionImpl expression = XExpressionImpl.fromText(logMessage);
431         XDebuggerHistoryManager.getInstance(myProject).addRecentExpression(XBreakpointActionsPanel.LOG_EXPRESSION_HISTORY_ID, expression);
432         myXBreakpoint.setLogExpressionObject(expression);
433         ((XBreakpointBase)myXBreakpoint).setLogExpressionEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "LOG_EXPRESSION_ENABLED")));
434       }
435     }
436     catch (Exception ignored) {
437     }
438     try {
439       setRemoveAfterHit(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "REMOVE_AFTER_HIT")));
440     }
441     catch (Exception ignored) {
442     }
443   }
444
445   @Nullable
446   public abstract PsiElement getEvaluationElement();
447
448   protected TextWithImports getLogMessage() {
449     return TextWithImportsImpl.fromXExpression(myXBreakpoint.getLogExpressionObject());
450   }
451
452   protected TextWithImports getCondition() {
453     return TextWithImportsImpl.fromXExpression(myXBreakpoint.getConditionExpression());
454   }
455
456   public boolean isEnabled() {
457     return myXBreakpoint.isEnabled();
458   }
459
460   public void setEnabled(boolean enabled) {
461     myXBreakpoint.setEnabled(enabled);
462   }
463
464   protected boolean isLogEnabled() {
465     return myXBreakpoint.isLogMessage();
466   }
467
468   public void setLogEnabled(boolean logEnabled) {
469     myXBreakpoint.setLogMessage(logEnabled);
470   }
471
472   protected boolean isLogExpressionEnabled() {
473     XExpression expression = myXBreakpoint.getLogExpressionObject();
474     if (XDebuggerUtilImpl.isEmptyExpression(expression)) {
475       return false;
476     }
477     return !getLogMessage().isEmpty();
478   }
479
480   @Override
481   public boolean isCountFilterEnabled() {
482     return getProperties().isCOUNT_FILTER_ENABLED();
483   }
484   public void setCountFilterEnabled(boolean enabled) {
485     if (getProperties().setCOUNT_FILTER_ENABLED(enabled)) {
486       fireBreakpointChanged();
487     }
488   }
489
490   @Override
491   public int getCountFilter() {
492     return getProperties().getCOUNT_FILTER();
493   }
494
495   public void setCountFilter(int filter) {
496     if (getProperties().setCOUNT_FILTER(filter)) {
497       fireBreakpointChanged();
498     }
499   }
500
501   @Override
502   public boolean isClassFiltersEnabled() {
503     return getProperties().isCLASS_FILTERS_ENABLED();
504   }
505
506   public void setClassFiltersEnabled(boolean enabled) {
507     if (getProperties().setCLASS_FILTERS_ENABLED(enabled)) {
508       fireBreakpointChanged();
509     }
510   }
511
512   @Override
513   public ClassFilter[] getClassFilters() {
514     return getProperties().getClassFilters();
515   }
516
517   public void setClassFilters(ClassFilter[] filters) {
518     if (getProperties().setClassFilters(filters)) {
519       fireBreakpointChanged();
520     }
521   }
522
523   @Override
524   public ClassFilter[] getClassExclusionFilters() {
525     return getProperties().getClassExclusionFilters();
526   }
527
528   public void setClassExclusionFilters(ClassFilter[] filters) {
529     if (getProperties().setClassExclusionFilters(filters)) {
530       fireBreakpointChanged();
531     }
532   }
533
534   @Override
535   public boolean isInstanceFiltersEnabled() {
536     return getProperties().isINSTANCE_FILTERS_ENABLED();
537   }
538
539   public void setInstanceFiltersEnabled(boolean enabled) {
540     if (getProperties().setINSTANCE_FILTERS_ENABLED(enabled)) {
541       fireBreakpointChanged();
542     }
543   }
544
545   @Override
546   public InstanceFilter[] getInstanceFilters() {
547     return getProperties().getInstanceFilters();
548   }
549
550   public void setInstanceFilters(InstanceFilter[] filters) {
551     if (getProperties().setInstanceFilters(filters)) {
552       fireBreakpointChanged();
553     }
554   }
555
556   private static String getSuspendPolicy(XBreakpoint breakpoint) {
557     switch (breakpoint.getSuspendPolicy()) {
558       case ALL:
559         return DebuggerSettings.SUSPEND_ALL;
560       case THREAD:
561         return DebuggerSettings.SUSPEND_THREAD;
562       case NONE:
563         return DebuggerSettings.SUSPEND_NONE;
564
565       default:
566         throw new IllegalArgumentException("unknown suspend policy");
567     }
568   }
569
570   static SuspendPolicy transformSuspendPolicy(String policy) {
571     if (DebuggerSettings.SUSPEND_ALL.equals(policy)) {
572       return SuspendPolicy.ALL;
573     } else if (DebuggerSettings.SUSPEND_THREAD.equals(policy)) {
574       return SuspendPolicy.THREAD;
575     } else if (DebuggerSettings.SUSPEND_NONE.equals(policy)) {
576       return SuspendPolicy.NONE;
577     } else {
578       throw new IllegalArgumentException("unknown suspend policy");
579     }
580   }
581
582   protected boolean isSuspend() {
583     return myXBreakpoint.getSuspendPolicy() != SuspendPolicy.NONE;
584   }
585
586   @Override
587   public String getSuspendPolicy() {
588     return getSuspendPolicy(myXBreakpoint);
589   }
590
591   public void setSuspendPolicy(String policy) {
592     myXBreakpoint.setSuspendPolicy(transformSuspendPolicy(policy));
593   }
594
595   protected boolean isConditionEnabled() {
596     XExpression condition = myXBreakpoint.getConditionExpression();
597     if (XDebuggerUtilImpl.isEmptyExpression(condition)) {
598       return false;
599     }
600     return !getCondition().isEmpty();
601   }
602
603   public void setCondition(@Nullable TextWithImports condition) {
604     myXBreakpoint.setConditionExpression(TextWithImportsImpl.toXExpression(condition));
605   }
606
607   public void addInstanceFilter(long l) {
608     getProperties().addInstanceFilter(l);
609   }
610
611   protected void fireBreakpointChanged() {
612     ((XBreakpointBase)myXBreakpoint).fireBreakpointChanged();
613   }
614 }