2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * Class LineBreakpoint
21 package com.intellij.debugger.ui.breakpoints;
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;
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;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.List;
64 public class LineBreakpoint extends BreakpointWithHighlighter {
65 private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.breakpoints.LineBreakpoint");
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");
75 private String myMethodName;
76 public static final @NonNls Key<LineBreakpoint> CATEGORY = BreakpointCategory.lookup("line_breakpoints");
78 protected LineBreakpoint(Project project) {
82 protected LineBreakpoint(Project project, RangeHighlighter highlighter) {
83 super(project, highlighter);
86 protected Icon getDisabledIcon(boolean isMuted) {
87 final Breakpoint master = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().findMasterBreakpoint(this);
89 return master == null? MUTED_DISABLED_ICON : DebuggerIcons.MUTED_DISABLED_DEPENDENT_BREAKPOINT_ICON;
92 return master == null? DISABLED_ICON : DebuggerIcons.DISABLED_DEPENDENT_BREAKPOINT_ICON;
96 protected Icon getSetIcon(boolean isMuted) {
97 return isMuted? MUTED_ICON : ICON;
100 protected Icon getInvalidIcon(boolean isMuted) {
101 return isMuted? DebuggerIcons.MUTED_INVALID_BREAKPOINT_ICON : DebuggerIcons.INVALID_BREAKPOINT_ICON;
104 protected Icon getVerifiedIcon(boolean isMuted) {
105 return isMuted? DebuggerIcons.MUTED_VERIFIED_BREAKPOINT_ICON : DebuggerIcons.VERIFIED_BREAKPOINT_ICON;
108 protected Icon getVerifiedWarningsIcon(boolean isMuted) {
109 return isMuted? ourMutedVerifiedWarningsIcon : ourVerifiedWarningsIcon;
112 public Key<LineBreakpoint> getCategory() {
116 protected void reload(PsiFile file) {
118 myMethodName = findMethodName(file, getHighlighter().getStartOffset());
121 protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) {
122 if (isInScopeOf(debugProcess, classToBeLoaded)) {
123 super.createOrWaitPrepare(debugProcess, classToBeLoaded);
127 protected void createRequestForPreparedClass(final DebugProcessImpl debugProcess, final ReferenceType classType) {
128 if (!isInScopeOf(debugProcess, classType.name())) {
131 ApplicationManager.getApplication().runReadAction(new Runnable() {
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()));
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());
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())
152 if (LOG.isDebugEnabled()) {
153 LOG.debug("No locations of type " + classType.name() + " found at line " + getLineIndex());
157 catch (ClassNotPreparedException ex) {
158 if (LOG.isDebugEnabled()) {
159 LOG.debug("ClassNotPreparedException: " + ex.getMessage());
161 // there's a chance to add a breakpoint when the class is prepared
163 catch (ObjectCollectedException ex) {
164 if (LOG.isDebugEnabled()) {
165 LOG.debug("ObjectCollectedException: " + ex.getMessage());
167 // there's a chance to add a breakpoint when the class is prepared
169 catch (InvalidLineNumberException ex) {
170 if (LOG.isDebugEnabled()) {
171 LOG.debug("InvalidLineNumberException: " + ex.getMessage());
173 debugProcess.getRequestsManager().setInvalid(LineBreakpoint.this, DebuggerBundle.message("error.invalid.breakpoint.bad.line.number"));
175 catch (InternalException ex) {
178 catch(Exception ex) {
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)) {
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();
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) {
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) {
237 String name = DebuggerUtilsEx.getQualifiedClassName(thisObject.referenceType().name(), getProject());
241 ClassFilter [] filters = getClassFilters();
242 boolean matches = false;
243 for (ClassFilter classFilter : filters) {
244 if (classFilter.isEnabled() && classFilter.matches(name)) {
253 ClassFilter [] ifilters = getClassExclusionFilters();
254 for (ClassFilter classFilter : ifilters) {
255 if (classFilter.isEnabled() && classFilter.matches(name)) {
260 return super.evaluateCondition(context, event);
263 public String toString() {
264 return getDescription();
268 public String getDisplayName() {
269 final int lineNumber = (getHighlighter().getDocument().getLineNumber(getHighlighter().getStartOffset()) + 1);
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();
277 String packageName = null;
279 final int dotIndex = className.lastIndexOf(".");
281 info.append(className.substring(dotIndex + 1));
282 packageName = className.substring(0, dotIndex);
285 info.append(className);
292 info.append(myMethodName);
294 if (packageName != null) {
295 info.append(" (").append(packageName).append(")");
297 return DebuggerBundle.message("line.breakpoint.display.name.with.class.or.method", lineNumber, info.toString());
300 StringBuilderSpinAllocator.dispose(info);
303 return DebuggerBundle.message("line.breakpoint.display.name", lineNumber);
305 return DebuggerBundle.message("status.breakpoint.invalid");
308 private static @Nullable String findMethodName(final PsiFile file, final int offset) {
309 if (file instanceof JspFile) {
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;
323 public String getEventMessage(LocatableEvent event) {
324 final Location location = event.location();
326 return DebuggerBundle.message(
327 "status.line.breakpoint.reached",
328 location.declaringType().name() + "." + location.method().name(),
329 location.sourceName(),
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(),
344 public PsiElement getEvaluationElement() {
345 return PositionUtil.getContextElement(getSourcePosition());
348 protected static LineBreakpoint create(Project project, Document document, int lineIndex) {
349 VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
350 if (virtualFile == null) return null;
352 LineBreakpoint breakpoint = new LineBreakpoint(project, createHighlighter(project, document, lineIndex));
353 return (LineBreakpoint)breakpoint.init();
356 public boolean canMoveTo(SourcePosition position) {
357 if (!super.canMoveTo(position)) {
360 final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(position.getFile());
361 return canAddLineBreakpoint(myProject, document, position.getLine());
364 public static boolean canAddLineBreakpoint(Project project, final Document document, final int lineIndex) {
365 if (lineIndex < 0 || lineIndex >= document.getLineCount()) {
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
374 PsiDocumentManager.getInstance(project).commitDocument(document);
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)) {
382 PsiElement child = element;
383 while(element != null) {
385 final int offset = element.getTextOffset();
387 if (document.getLineNumber(offset) != lineIndex) {
392 element = element.getParent();
395 if(child instanceof PsiMethod && child.getTextRange().getEndOffset() >= document.getLineEndOffset(lineIndex)) {
396 PsiCodeBlock body = ((PsiMethod)child).getBody();
401 PsiStatement[] statements = body.getStatements();
402 canAdd[0] = statements.length > 0 && document.getLineNumber(statements[0].getTextOffset()) == lineIndex;
415 public @Nullable String getMethodName() {