f7ca6e3b3f117843642db2fb36311d950bd11700
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / breakpoints / LineBreakpoint.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
17 /*
18  * Class LineBreakpoint
19  * @author Jeka
20  */
21 package com.intellij.debugger.ui.breakpoints;
22
23 import com.intellij.debugger.DebuggerBundle;
24 import com.intellij.debugger.DebuggerManagerEx;
25 import com.intellij.debugger.SourcePosition;
26 import com.intellij.debugger.engine.DebugProcessImpl;
27 import com.intellij.debugger.engine.evaluation.EvaluateException;
28 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
29 import com.intellij.debugger.impl.DebuggerUtilsEx;
30 import com.intellij.debugger.impl.PositionUtil;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.editor.Document;
34 import com.intellij.openapi.editor.markup.RangeHighlighter;
35 import com.intellij.openapi.fileEditor.FileDocumentManager;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.util.Computable;
38 import com.intellij.openapi.util.IconLoader;
39 import com.intellij.openapi.util.Key;
40 import com.intellij.openapi.vfs.LocalFileSystem;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.psi.*;
43 import com.intellij.psi.jsp.JspFile;
44 import com.intellij.psi.search.GlobalSearchScope;
45 import com.intellij.psi.util.PsiTreeUtil;
46 import com.intellij.ui.classFilter.ClassFilter;
47 import com.intellij.util.Processor;
48 import com.intellij.util.StringBuilderSpinAllocator;
49 import com.intellij.xdebugger.XDebuggerUtil;
50 import com.intellij.xdebugger.ui.DebuggerIcons;
51 import com.sun.jdi.*;
52 import com.sun.jdi.event.LocatableEvent;
53 import com.sun.jdi.request.BreakpointRequest;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.List;
63
64 public class LineBreakpoint extends BreakpointWithHighlighter {
65   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.breakpoints.LineBreakpoint");
66
67   // icons
68   public static Icon ICON = DebuggerIcons.ENABLED_BREAKPOINT_ICON;
69   public static final Icon MUTED_ICON = DebuggerIcons.MUTED_BREAKPOINT_ICON;
70   public static final Icon DISABLED_ICON = DebuggerIcons.DISABLED_BREAKPOINT_ICON;
71   public static final Icon MUTED_DISABLED_ICON = DebuggerIcons.MUTED_DISABLED_BREAKPOINT_ICON;
72   private static final Icon ourVerifiedWarningsIcon = IconLoader.getIcon("/debugger/db_verified_warning_breakpoint.png");
73   private static final Icon ourMutedVerifiedWarningsIcon = IconLoader.getIcon("/debugger/db_muted_verified_warning_breakpoint.png");
74
75   private String myMethodName;
76   public static final @NonNls Key<LineBreakpoint> CATEGORY = BreakpointCategory.lookup("line_breakpoints");
77
78   protected LineBreakpoint(Project project) {
79     super(project);
80   }
81
82   protected LineBreakpoint(Project project, RangeHighlighter highlighter) {
83     super(project, highlighter);
84   }
85
86   protected Icon getDisabledIcon(boolean isMuted) {
87     final Breakpoint master = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().findMasterBreakpoint(this);
88     if (isMuted) {
89       return master == null? MUTED_DISABLED_ICON : DebuggerIcons.MUTED_DISABLED_DEPENDENT_BREAKPOINT_ICON;
90     }
91     else {
92       return master == null? DISABLED_ICON : DebuggerIcons.DISABLED_DEPENDENT_BREAKPOINT_ICON;
93     }
94   }
95
96   protected Icon getSetIcon(boolean isMuted) {
97     return isMuted? MUTED_ICON : ICON;
98   }
99
100   protected Icon getInvalidIcon(boolean isMuted) {
101     return isMuted? DebuggerIcons.MUTED_INVALID_BREAKPOINT_ICON : DebuggerIcons.INVALID_BREAKPOINT_ICON;
102   }
103
104   protected Icon getVerifiedIcon(boolean isMuted) {
105     return isMuted? DebuggerIcons.MUTED_VERIFIED_BREAKPOINT_ICON : DebuggerIcons.VERIFIED_BREAKPOINT_ICON;
106   }
107
108   protected Icon getVerifiedWarningsIcon(boolean isMuted) {
109     return isMuted? ourMutedVerifiedWarningsIcon : ourVerifiedWarningsIcon;
110   }
111
112   public Key<LineBreakpoint> getCategory() {
113     return CATEGORY;
114   }
115
116   protected void reload(PsiFile file) {
117     super.reload(file);
118     myMethodName = findMethodName(file, getHighlighter().getStartOffset());
119   }
120
121   protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) {
122     if (isInScopeOf(debugProcess, classToBeLoaded)) {
123       super.createOrWaitPrepare(debugProcess, classToBeLoaded);
124     }
125   }
126
127   protected void createRequestForPreparedClass(final DebugProcessImpl debugProcess, final ReferenceType classType) {
128     if (!isInScopeOf(debugProcess, classType.name())) {
129       return;
130     }
131     ApplicationManager.getApplication().runReadAction(new Runnable() {
132       public void run() {
133         try {
134           List<Location> locs = debugProcess.getPositionManager().locationsOfLine(classType, getSourcePosition());
135           if (locs.size() > 0) {
136             for (final Location location : locs) {
137               if (LOG.isDebugEnabled()) {
138                 LOG.debug("Found location for reference type " + classType.name() + " at line " + getLineIndex() + "; isObsolete: " + (debugProcess.getVirtualMachineProxy().versionHigher("1.4") && location.method().isObsolete()));
139               }
140               BreakpointRequest request = debugProcess.getRequestsManager().createBreakpointRequest(LineBreakpoint.this, location);
141               debugProcess.getRequestsManager().enableRequest(request);
142               if (LOG.isDebugEnabled()) {
143                 LOG.debug("Created breakpoint request for reference type " + classType.name() + " at line " + getLineIndex());
144               }
145             }
146           }
147           else {
148             // there's no executable code in this class
149             debugProcess.getRequestsManager().setInvalid(LineBreakpoint.this, DebuggerBundle.message(
150               "error.invalid.breakpoint.no.executable.code", (getLineIndex() + 1), classType.name())
151             );
152             if (LOG.isDebugEnabled()) {
153               LOG.debug("No locations of type " + classType.name() + " found at line " + getLineIndex());
154             }
155           }
156         }
157         catch (ClassNotPreparedException ex) {
158           if (LOG.isDebugEnabled()) {
159             LOG.debug("ClassNotPreparedException: " + ex.getMessage());
160           }
161           // there's a chance to add a breakpoint when the class is prepared
162         }
163         catch (ObjectCollectedException ex) {
164           if (LOG.isDebugEnabled()) {
165             LOG.debug("ObjectCollectedException: " + ex.getMessage());
166           }
167           // there's a chance to add a breakpoint when the class is prepared
168         }
169         catch (InvalidLineNumberException ex) {
170           if (LOG.isDebugEnabled()) {
171             LOG.debug("InvalidLineNumberException: " + ex.getMessage());
172           }
173           debugProcess.getRequestsManager().setInvalid(LineBreakpoint.this, DebuggerBundle.message("error.invalid.breakpoint.bad.line.number"));
174         }
175         catch (InternalException ex) {
176           LOG.info(ex);
177         }
178         catch(Exception ex) {
179           LOG.info(ex);
180         }
181         updateUI();
182       }
183     });
184   }
185
186   private boolean isInScopeOf(DebugProcessImpl debugProcess, String className) {
187     final SourcePosition position = getSourcePosition();
188     if (position != null) {
189       final VirtualFile breakpointFile = position.getFile().getVirtualFile();
190       if (breakpointFile != null) {
191         final Collection<VirtualFile> candidates = findClassFileCandidates(className, debugProcess.getSearchScope());
192         if (!candidates.isEmpty()) {
193           for (VirtualFile classFile : candidates) {
194             if (breakpointFile.equals(classFile)) {
195               return true;
196             }
197           }
198           return false;
199         }
200       }
201     }
202     return true;
203   }
204
205   @NotNull
206   private Collection<VirtualFile> findClassFileCandidates(final String className, final GlobalSearchScope scope) {
207     final int dollarIndex = className.indexOf("$");
208     final String topLevelClassName = dollarIndex >= 0? className.substring(0, dollarIndex) : className;
209     return ApplicationManager.getApplication().runReadAction(new Computable<Collection<VirtualFile>>() {
210       public Collection<VirtualFile> compute() {
211         final PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(topLevelClassName, scope);
212         if (classes.length == 0) {
213           return Collections.emptyList();
214         }
215         final List<VirtualFile> list = new ArrayList<VirtualFile>(classes.length);
216         for (PsiClass aClass : classes) {
217           final PsiFile psiFile = aClass.getContainingFile();
218           if (psiFile != null) {
219             final VirtualFile vFile = psiFile.getVirtualFile();
220             if (vFile != null && vFile.getFileSystem() instanceof LocalFileSystem) {
221               list.add(vFile);
222             }
223           }
224         }
225         return list;
226       }
227     });
228   }
229
230   public boolean evaluateCondition(EvaluationContextImpl context, LocatableEvent event) throws EvaluateException {
231     if(CLASS_FILTERS_ENABLED){
232       Value value = context.getThisObject();
233       ObjectReference thisObject = (ObjectReference)value;
234       if(thisObject == null) {
235         return false;
236       }
237       String name = DebuggerUtilsEx.getQualifiedClassName(thisObject.referenceType().name(), getProject());
238       if(name == null) {
239         return false;
240       }
241       ClassFilter [] filters = getClassFilters();
242       boolean matches = false;
243       for (ClassFilter classFilter : filters) {
244         if (classFilter.isEnabled() && classFilter.matches(name)) {
245           matches = true;
246           break;
247         }
248       }
249       if(!matches) {
250         return false;
251       }
252
253       ClassFilter [] ifilters = getClassExclusionFilters();
254       for (ClassFilter classFilter : ifilters) {
255         if (classFilter.isEnabled() && classFilter.matches(name)) {
256           return false;
257         }
258       }
259     }
260     return super.evaluateCondition(context, event);
261   }
262
263   public String toString() {
264     return getDescription();
265   }
266
267
268   public String getDisplayName() {
269     final int lineNumber = (getHighlighter().getDocument().getLineNumber(getHighlighter().getStartOffset()) + 1);
270     if(isValid()) {
271       final String className = getClassName();
272       final boolean hasClassInfo = className != null && className.length() > 0;
273       final boolean hasMethodInfo = myMethodName != null && myMethodName.length() > 0;
274       if (hasClassInfo || hasMethodInfo) {
275         final StringBuilder info = StringBuilderSpinAllocator.alloc();
276         try {
277           String packageName = null;
278           if (hasClassInfo) {
279             final int dotIndex = className.lastIndexOf(".");
280             if (dotIndex >= 0) {
281               info.append(className.substring(dotIndex + 1));
282               packageName = className.substring(0, dotIndex);
283             }
284             else {
285               info.append(className);
286             }
287           }
288           if(hasMethodInfo) {
289             if (hasClassInfo) {
290               info.append(".");
291             }
292             info.append(myMethodName);
293           }
294           if (packageName != null) {
295             info.append(" (").append(packageName).append(")");
296           }
297           return DebuggerBundle.message("line.breakpoint.display.name.with.class.or.method", lineNumber, info.toString());
298         }
299         finally {
300           StringBuilderSpinAllocator.dispose(info);
301         }
302       }
303       return DebuggerBundle.message("line.breakpoint.display.name", lineNumber);
304     }
305     return DebuggerBundle.message("status.breakpoint.invalid");
306   }
307
308   private static @Nullable String findMethodName(final PsiFile file, final int offset) {
309     if (file instanceof JspFile) {
310       return null;
311     }
312     if (file instanceof PsiJavaFile) {
313       return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
314         public String compute() {
315           final PsiMethod method = DebuggerUtilsEx.findPsiMethod(file, offset);
316           return method != null? method.getName() + "()" : null;
317         }
318       });
319     }
320     return null;
321   }
322
323   public String getEventMessage(LocatableEvent event) {
324     final Location location = event.location();
325     try {
326       return DebuggerBundle.message(
327         "status.line.breakpoint.reached", 
328         location.declaringType().name() + "." + location.method().name(), 
329         location.sourceName(), 
330         getLineIndex() + 1
331       );
332     }
333     catch (AbsentInformationException e) {
334       final String sourceName = getSourcePosition().getFile().getName();
335       return DebuggerBundle.message(
336         "status.line.breakpoint.reached", 
337         location.declaringType().name() + "." + location.method().name(), 
338         sourceName, 
339         getLineIndex() + 1
340       );
341     }
342   }
343
344   public PsiElement getEvaluationElement() {
345     return PositionUtil.getContextElement(getSourcePosition());
346   }
347
348   protected static LineBreakpoint create(Project project, Document document, int lineIndex) {
349     VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
350     if (virtualFile == null) return null;
351
352     LineBreakpoint breakpoint = new LineBreakpoint(project, createHighlighter(project, document, lineIndex));
353     return (LineBreakpoint)breakpoint.init();
354   }
355
356   public boolean canMoveTo(SourcePosition position) {
357     if (!super.canMoveTo(position)) {
358       return false;
359     }
360     final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(position.getFile());
361     return canAddLineBreakpoint(myProject, document, position.getLine());
362   }
363
364   public static boolean canAddLineBreakpoint(Project project, final Document document, final int lineIndex) {
365     if (lineIndex < 0 || lineIndex >= document.getLineCount()) {
366       return false;
367     }
368     final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(project).getBreakpointManager();
369     final LineBreakpoint breakpointAtLine = breakpointManager.findBreakpoint( document, document.getLineStartOffset(lineIndex), CATEGORY);
370     if (breakpointAtLine != null) {
371       // there already exists a line breakpoint at this line
372       return false;
373     }
374     PsiDocumentManager.getInstance(project).commitDocument(document);
375
376     final boolean[] canAdd = new boolean[]{false};
377     XDebuggerUtil.getInstance().iterateLine(project, document, lineIndex, new Processor<PsiElement>() {
378       public boolean process(PsiElement element) {
379         if ((element instanceof PsiWhiteSpace) || (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null)) {
380           return true;
381         }
382         PsiElement child = element;
383         while(element != null) {
384
385           final int offset = element.getTextOffset();
386           if (offset >= 0) {
387             if (document.getLineNumber(offset) != lineIndex) {
388               break;
389             }
390           }
391           child = element;
392           element = element.getParent();
393         }
394
395         if(child instanceof PsiMethod && child.getTextRange().getEndOffset() >= document.getLineEndOffset(lineIndex)) {
396           PsiCodeBlock body = ((PsiMethod)child).getBody();
397           if(body == null) {
398             canAdd[0] = false;
399           }
400           else {
401             PsiStatement[] statements = body.getStatements();
402             canAdd[0] = statements.length > 0 && document.getLineNumber(statements[0].getTextOffset()) == lineIndex;
403           }
404         }
405         else {
406           canAdd[0] = true;
407         }
408         return false;
409       }
410     });
411
412     return canAdd[0];
413   }
414
415   public @Nullable String getMethodName() {
416     return myMethodName;
417   }
418 }