| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Class Breakpoint |
| * @author Jeka |
| */ |
| package com.intellij.debugger.ui.breakpoints; |
| |
| import com.intellij.debugger.*; |
| import com.intellij.debugger.engine.*; |
| import com.intellij.debugger.engine.evaluation.*; |
| import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl; |
| import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator; |
| import com.intellij.debugger.engine.events.SuspendContextCommandImpl; |
| import com.intellij.debugger.engine.requests.RequestManagerImpl; |
| import com.intellij.debugger.jdi.StackFrameProxyImpl; |
| import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; |
| import com.intellij.debugger.requests.ClassPrepareRequestor; |
| import com.intellij.debugger.settings.DebuggerSettings; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.InvalidDataException; |
| import com.intellij.openapi.util.JDOMExternalizerUtil; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.psi.PsiClass; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.ui.AppUIUtil; |
| import com.intellij.ui.classFilter.ClassFilter; |
| import com.intellij.util.StringBuilderSpinAllocator; |
| import com.intellij.util.ThreeState; |
| import com.intellij.xdebugger.XExpression; |
| import com.intellij.xdebugger.breakpoints.SuspendPolicy; |
| import com.intellij.xdebugger.breakpoints.XBreakpoint; |
| import com.intellij.xdebugger.breakpoints.XLineBreakpoint; |
| import com.intellij.xdebugger.impl.XDebugSessionImpl; |
| import com.intellij.xdebugger.impl.XDebuggerHistoryManager; |
| import com.intellij.xdebugger.impl.XDebuggerUtilImpl; |
| import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase; |
| import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl; |
| import com.intellij.xdebugger.impl.breakpoints.ui.XBreakpointActionsPanel; |
| import com.sun.jdi.*; |
| import com.sun.jdi.event.LocatableEvent; |
| import org.jdom.Element; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties; |
| |
| import javax.swing.*; |
| import java.util.List; |
| |
| public abstract class Breakpoint<P extends JavaBreakpointProperties> implements FilteredRequestor, ClassPrepareRequestor { |
| public static Key<Breakpoint> DATA_KEY = Key.create("JavaBreakpoint"); |
| |
| final XBreakpoint<P> myXBreakpoint; |
| protected final Project myProject; |
| |
| @NonNls private static final String LOG_MESSAGE_OPTION_NAME = "LOG_MESSAGE"; |
| public static final Breakpoint[] EMPTY_ARRAY = new Breakpoint[0]; |
| protected boolean myCachedVerifiedState = false; |
| |
| protected Breakpoint(@NotNull Project project, XBreakpoint<P> xBreakpoint) { |
| myProject = project; |
| myXBreakpoint = xBreakpoint; |
| } |
| |
| public Project getProject() { |
| return myProject; |
| } |
| |
| protected P getProperties() { |
| return myXBreakpoint.getProperties(); |
| } |
| |
| public XBreakpoint<P> getXBreakpoint() { |
| return myXBreakpoint; |
| } |
| |
| @Nullable |
| public abstract PsiClass getPsiClass(); |
| /** |
| * Request for creating all needed JPDA requests in the specified VM |
| * @param debuggerProcess the requesting process |
| */ |
| public abstract void createRequest(DebugProcessImpl debugProcess); |
| |
| protected boolean shouldCreateRequest(final DebugProcessImpl debugProcess) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| JavaDebugProcess process = debugProcess.getXdebugProcess(); |
| return process != null |
| && debugProcess.isAttached() |
| && ((XDebugSessionImpl)process.getSession()).isBreakpointActive(myXBreakpoint) |
| && debugProcess.getRequestsManager().findRequests(Breakpoint.this).isEmpty(); |
| } |
| }); |
| } |
| |
| /** |
| * Request for creating all needed JPDA requests in the specified VM |
| * @param debuggerProcess the requesting process |
| */ |
| @Override |
| public abstract void processClassPrepare(DebugProcess debuggerProcess, final ReferenceType referenceType); |
| |
| public abstract String getDisplayName (); |
| |
| public String getShortName() { |
| return getDisplayName(); |
| } |
| |
| @Nullable |
| public String getClassName() { |
| return null; |
| } |
| |
| public void markVerified(boolean isVerified) { |
| myCachedVerifiedState = isVerified; |
| } |
| |
| public boolean isRemoveAfterHit() { |
| return myXBreakpoint instanceof XLineBreakpoint && ((XLineBreakpoint)myXBreakpoint).isTemporary(); |
| } |
| |
| public void setRemoveAfterHit(boolean value) { |
| if (myXBreakpoint instanceof XLineBreakpoint) { |
| ((XLineBreakpoint)myXBreakpoint).setTemporary(value); |
| } |
| } |
| |
| @Nullable |
| public String getShortClassName() { |
| final String className = getClassName(); |
| if (className == null) { |
| return null; |
| } |
| |
| final int dotIndex = className.lastIndexOf('.'); |
| return dotIndex >= 0 && dotIndex + 1 < className.length() ? className.substring(dotIndex + 1) : className; |
| } |
| |
| @Nullable |
| public String getPackageName() { |
| return null; |
| } |
| |
| public abstract Icon getIcon(); |
| |
| public abstract void reload(); |
| |
| /** |
| * returns UI representation |
| */ |
| public abstract String getEventMessage(LocatableEvent event); |
| |
| public abstract boolean isValid(); |
| |
| public abstract Key<? extends Breakpoint> getCategory(); |
| |
| /** |
| * Associates breakpoint with class. |
| * Create requests for loaded class and registers callback for loading classes |
| * @param debugProcess the requesting process |
| */ |
| protected void createOrWaitPrepare(DebugProcessImpl debugProcess, String classToBeLoaded) { |
| debugProcess.getRequestsManager().callbackOnPrepareClasses(this, classToBeLoaded); |
| |
| List list = debugProcess.getVirtualMachineProxy().classesByName(classToBeLoaded); |
| for (final Object aList : list) { |
| ReferenceType refType = (ReferenceType)aList; |
| if (refType.isPrepared()) { |
| processClassPrepare(debugProcess, refType); |
| } |
| } |
| } |
| |
| protected void createOrWaitPrepare(final DebugProcessImpl debugProcess, @NotNull final SourcePosition classPosition) { |
| debugProcess.getRequestsManager().callbackOnPrepareClasses(this, classPosition); |
| |
| for (ReferenceType refType : debugProcess.getPositionManager().getAllClasses(classPosition)) { |
| if (refType.isPrepared()) { |
| processClassPrepare(debugProcess, refType); |
| } |
| } |
| } |
| |
| protected ObjectReference getThisObject(SuspendContextImpl context, LocatableEvent event) throws EvaluateException { |
| ThreadReferenceProxyImpl thread = context.getThread(); |
| if(thread != null) { |
| StackFrameProxyImpl stackFrameProxy = thread.frame(0); |
| if(stackFrameProxy != null) { |
| return stackFrameProxy.thisObject(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean processLocatableEvent(final SuspendContextCommandImpl action, final LocatableEvent event) throws EventProcessingException { |
| final SuspendContextImpl context = action.getSuspendContext(); |
| if(!isValid()) { |
| context.getDebugProcess().getRequestsManager().deleteRequest(this); |
| return false; |
| } |
| |
| final String[] title = {DebuggerBundle.message("title.error.evaluating.breakpoint.condition") }; |
| |
| try { |
| final StackFrameProxyImpl frameProxy = context.getThread().frame(0); |
| if (frameProxy == null) { |
| // might be if the thread has been collected |
| return false; |
| } |
| |
| final EvaluationContextImpl evaluationContext = new EvaluationContextImpl( |
| action.getSuspendContext(), |
| frameProxy, |
| getThisObject(context, event) |
| ); |
| |
| if(!evaluateCondition(evaluationContext, event)) { |
| return false; |
| } |
| |
| title[0] = DebuggerBundle.message("title.error.evaluating.breakpoint.action"); |
| runAction(evaluationContext, event); |
| } |
| catch (final EvaluateException ex) { |
| if(ApplicationManager.getApplication().isUnitTestMode()) { |
| System.out.println(ex.getMessage()); |
| return false; |
| } |
| |
| throw new EventProcessingException(title[0], ex.getMessage(), ex); |
| } |
| |
| return true; |
| } |
| |
| private void runAction(final EvaluationContextImpl context, LocatableEvent event) { |
| final DebugProcessImpl debugProcess = context.getDebugProcess(); |
| if (isLogEnabled() || isLogExpressionEnabled()) { |
| final StringBuilder buf = StringBuilderSpinAllocator.alloc(); |
| try { |
| if (myXBreakpoint.isLogMessage()) { |
| buf.append(getEventMessage(event)); |
| buf.append("\n"); |
| } |
| if (isLogExpressionEnabled()) { |
| if(!debugProcess.isAttached()) { |
| return; |
| } |
| |
| final TextWithImports expressionToEvaluate = getLogMessage(); |
| try { |
| ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(getProject(), new EvaluatingComputable<ExpressionEvaluator>() { |
| @Override |
| public ExpressionEvaluator compute() throws EvaluateException { |
| return EvaluatorBuilderImpl.build(expressionToEvaluate, ContextUtil.getContextElement(context), ContextUtil.getSourcePosition(context)); |
| } |
| }); |
| final Value eval = evaluator.evaluate(context); |
| final String result = eval instanceof VoidValue ? "void" : DebuggerUtils.getValueAsString(context, eval); |
| buf.append(result); |
| } |
| catch (EvaluateException e) { |
| buf.append(DebuggerBundle.message("error.unable.to.evaluate.expression")); |
| buf.append(" \""); |
| buf.append(expressionToEvaluate); |
| buf.append("\""); |
| buf.append(" : "); |
| buf.append(e.getMessage()); |
| } |
| buf.append("\n"); |
| } |
| if (buf.length() > 0) { |
| debugProcess.printToConsole(buf.toString()); |
| } |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(buf); |
| } |
| } |
| if (isRemoveAfterHit()) { |
| handleTemporaryBreakpointHit(debugProcess); |
| } |
| } |
| |
| /** |
| * @return true if the ID was added or false otherwise |
| */ |
| private boolean hasObjectID(long id) { |
| for (InstanceFilter instanceFilter : getInstanceFilters()) { |
| if (instanceFilter.getId() == id) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean evaluateCondition(final EvaluationContextImpl context, LocatableEvent event) throws EvaluateException { |
| final DebugProcessImpl debugProcess = context.getDebugProcess(); |
| if (isCountFilterEnabled()) { |
| debugProcess.getVirtualMachineProxy().suspend(); |
| debugProcess.getRequestsManager().deleteRequest(this); |
| ((Breakpoint)this).createRequest(debugProcess); |
| debugProcess.getVirtualMachineProxy().resume(); |
| } |
| if (isInstanceFiltersEnabled()) { |
| Value value = context.getThisObject(); |
| if (value != null) { // non-static |
| ObjectReference reference = (ObjectReference)value; |
| if (!hasObjectID(reference.uniqueID())) { |
| return false; |
| } |
| } |
| } |
| |
| if (isClassFiltersEnabled()) { |
| String typeName = calculateEventClass(context, event); |
| if (!typeMatchesClassFilters(typeName)) return false; |
| } |
| |
| if (!isConditionEnabled() || getCondition().getText().isEmpty()) { |
| return true; |
| } |
| |
| StackFrameProxyImpl frame = context.getFrameProxy(); |
| if (frame != null) { |
| Location location = frame.location(); |
| if (location != null) { |
| ThreeState result = debugProcess.getPositionManager().evaluateCondition(context, frame, location, getCondition().getText()); |
| if (result != ThreeState.UNSURE) { |
| return result == ThreeState.YES; |
| } |
| } |
| } |
| |
| try { |
| ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(context.getProject(), new EvaluatingComputable<ExpressionEvaluator>() { |
| @Override |
| public ExpressionEvaluator compute() throws EvaluateException { |
| final SourcePosition contextSourcePosition = ContextUtil.getSourcePosition(context); |
| // IMPORTANT: calculate context psi element basing on the location where the exception |
| // has been hit, not on the location where it was set. (For line breakpoints these locations are the same, however, |
| // for method, exception and field breakpoints these locations differ) |
| PsiElement contextPsiElement = ContextUtil.getContextElement(contextSourcePosition); |
| if (contextPsiElement == null) { |
| contextPsiElement = getEvaluationElement(); // as a last resort |
| } |
| return EvaluatorBuilderImpl.build(getCondition(), contextPsiElement, contextSourcePosition); |
| } |
| }); |
| final Value value = evaluator.evaluate(context); |
| if (!(value instanceof BooleanValue)) { |
| throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.boolean.expected")); |
| } |
| if (!((BooleanValue)value).booleanValue()) { |
| return false; |
| } |
| } |
| catch (EvaluateException ex) { |
| if (ex.getCause() instanceof VMDisconnectedException) { |
| return false; |
| } |
| throw EvaluateExceptionUtil.createEvaluateException( |
| DebuggerBundle.message("error.failed.evaluating.breakpoint.condition", getCondition(), ex.getMessage()) |
| ); |
| } |
| return true; |
| } |
| |
| protected String calculateEventClass(EvaluationContextImpl context, LocatableEvent event) throws EvaluateException { |
| return event.location().declaringType().name(); |
| } |
| |
| private boolean typeMatchesClassFilters(@Nullable String typeName) { |
| if (typeName == null) { |
| return true; |
| } |
| boolean matches = false, hasEnabled = false; |
| for (ClassFilter classFilter : getClassFilters()) { |
| if (classFilter.isEnabled()) { |
| hasEnabled = true; |
| if (classFilter.matches(typeName)) { |
| matches = true; |
| break; |
| } |
| } |
| } |
| if(hasEnabled && !matches) { |
| return false; |
| } |
| for (ClassFilter classFilter : getClassExclusionFilters()) { |
| if (classFilter.isEnabled() && classFilter.matches(typeName)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void handleTemporaryBreakpointHit(final DebugProcessImpl debugProcess) { |
| debugProcess.addDebugProcessListener(new DebugProcessAdapter() { |
| @Override |
| public void resumed(SuspendContext suspendContext) { |
| removeBreakpoint(); |
| } |
| |
| @Override |
| public void processDetached(DebugProcess process, boolean closedByUser) { |
| removeBreakpoint(); |
| } |
| |
| private void removeBreakpoint() { |
| AppUIUtil.invokeOnEdt(new Runnable() { |
| @Override |
| public void run() { |
| DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().removeBreakpoint(Breakpoint.this); |
| } |
| }); |
| debugProcess.removeDebugProcessListener(this); |
| } |
| }); |
| } |
| |
| public void updateUI() { |
| } |
| |
| public void delete() { |
| RequestManagerImpl.deleteRequests(this); |
| } |
| |
| public void readExternal(Element parentNode) throws InvalidDataException { |
| FilteredRequestorImpl requestor = new FilteredRequestorImpl(myProject); |
| requestor.readTo(parentNode, this); |
| try { |
| setEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "ENABLED"))); |
| } |
| catch (Exception ignored) { |
| } |
| try { |
| setLogEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "LOG_ENABLED"))); |
| } |
| catch (Exception ignored) { |
| } |
| try { |
| String logMessage = JDOMExternalizerUtil.readField(parentNode, LOG_MESSAGE_OPTION_NAME); |
| if (logMessage != null && !logMessage.isEmpty()) { |
| XExpressionImpl expression = XExpressionImpl.fromText(logMessage); |
| XDebuggerHistoryManager.getInstance(myProject).addRecentExpression(XBreakpointActionsPanel.LOG_EXPRESSION_HISTORY_ID, expression); |
| myXBreakpoint.setLogExpressionObject(expression); |
| ((XBreakpointBase)myXBreakpoint).setLogExpressionEnabled(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "LOG_EXPRESSION_ENABLED"))); |
| } |
| } |
| catch (Exception ignored) { |
| } |
| try { |
| setRemoveAfterHit(Boolean.valueOf(JDOMExternalizerUtil.readField(parentNode, "REMOVE_AFTER_HIT"))); |
| } |
| catch (Exception ignored) { |
| } |
| } |
| |
| @Nullable |
| public abstract PsiElement getEvaluationElement(); |
| |
| protected TextWithImports getLogMessage() { |
| return TextWithImportsImpl.fromXExpression(myXBreakpoint.getLogExpressionObject()); |
| } |
| |
| protected TextWithImports getCondition() { |
| return TextWithImportsImpl.fromXExpression(myXBreakpoint.getConditionExpression()); |
| } |
| |
| public boolean isEnabled() { |
| return myXBreakpoint.isEnabled(); |
| } |
| |
| public void setEnabled(boolean enabled) { |
| myXBreakpoint.setEnabled(enabled); |
| } |
| |
| protected boolean isLogEnabled() { |
| return myXBreakpoint.isLogMessage(); |
| } |
| |
| public void setLogEnabled(boolean logEnabled) { |
| myXBreakpoint.setLogMessage(logEnabled); |
| } |
| |
| protected boolean isLogExpressionEnabled() { |
| XExpression expression = myXBreakpoint.getLogExpressionObject(); |
| if (XDebuggerUtilImpl.isEmptyExpression(expression)) { |
| return false; |
| } |
| return !getLogMessage().isEmpty(); |
| } |
| |
| @Override |
| public boolean isCountFilterEnabled() { |
| if (getProperties() == null) { |
| return false; |
| } |
| return getProperties().isCOUNT_FILTER_ENABLED(); |
| } |
| public void setCountFilterEnabled(boolean enabled) { |
| if (getProperties().setCOUNT_FILTER_ENABLED(enabled)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| @Override |
| public int getCountFilter() { |
| return getProperties().getCOUNT_FILTER(); |
| } |
| |
| public void setCountFilter(int filter) { |
| if (getProperties().setCOUNT_FILTER(filter)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| @Override |
| public boolean isClassFiltersEnabled() { |
| if (getProperties() == null) { |
| return false; |
| } |
| return getProperties().isCLASS_FILTERS_ENABLED(); |
| } |
| |
| public void setClassFiltersEnabled(boolean enabled) { |
| if (getProperties().setCLASS_FILTERS_ENABLED(enabled)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| @Override |
| public ClassFilter[] getClassFilters() { |
| return getProperties().getClassFilters(); |
| } |
| |
| public void setClassFilters(ClassFilter[] filters) { |
| if (getProperties().setClassFilters(filters)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| @Override |
| public ClassFilter[] getClassExclusionFilters() { |
| return getProperties().getClassExclusionFilters(); |
| } |
| |
| protected void setClassExclusionFilters(ClassFilter[] filters) { |
| if (getProperties().setClassExclusionFilters(filters)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| @Override |
| public boolean isInstanceFiltersEnabled() { |
| if (getProperties() == null) { |
| return false; |
| } |
| return getProperties().isINSTANCE_FILTERS_ENABLED(); |
| } |
| |
| public void setInstanceFiltersEnabled(boolean enabled) { |
| if (getProperties().setINSTANCE_FILTERS_ENABLED(enabled)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| @Override |
| public InstanceFilter[] getInstanceFilters() { |
| return getProperties().getInstanceFilters(); |
| } |
| |
| public void setInstanceFilters(InstanceFilter[] filters) { |
| if (getProperties().setInstanceFilters(filters)) { |
| fireBreakpointChanged(); |
| } |
| } |
| |
| private static String getSuspendPolicy(XBreakpoint breakpoint) { |
| switch (breakpoint.getSuspendPolicy()) { |
| case ALL: |
| return DebuggerSettings.SUSPEND_ALL; |
| case THREAD: |
| return DebuggerSettings.SUSPEND_THREAD; |
| case NONE: |
| return DebuggerSettings.SUSPEND_NONE; |
| |
| default: |
| throw new IllegalArgumentException("unknown suspend policy"); |
| } |
| } |
| |
| static SuspendPolicy transformSuspendPolicy(String policy) { |
| if (DebuggerSettings.SUSPEND_ALL.equals(policy)) { |
| return SuspendPolicy.ALL; |
| } else if (DebuggerSettings.SUSPEND_THREAD.equals(policy)) { |
| return SuspendPolicy.THREAD; |
| } else if (DebuggerSettings.SUSPEND_NONE.equals(policy)) { |
| return SuspendPolicy.NONE; |
| } else { |
| throw new IllegalArgumentException("unknown suspend policy"); |
| } |
| } |
| |
| protected boolean isSuspend() { |
| return myXBreakpoint.getSuspendPolicy() != SuspendPolicy.NONE; |
| } |
| |
| @Override |
| public String getSuspendPolicy() { |
| return getSuspendPolicy(myXBreakpoint); |
| } |
| |
| public void setSuspendPolicy(String policy) { |
| myXBreakpoint.setSuspendPolicy(transformSuspendPolicy(policy)); |
| } |
| |
| protected boolean isConditionEnabled() { |
| XExpression condition = myXBreakpoint.getConditionExpression(); |
| if (XDebuggerUtilImpl.isEmptyExpression(condition)) { |
| return false; |
| } |
| return !getCondition().isEmpty(); |
| } |
| |
| public void setCondition(@Nullable TextWithImports condition) { |
| myXBreakpoint.setConditionExpression(TextWithImportsImpl.toXExpression(condition)); |
| } |
| |
| protected void addInstanceFilter(long l) { |
| getProperties().addInstanceFilter(l); |
| } |
| |
| protected void fireBreakpointChanged() { |
| ((XBreakpointBase)myXBreakpoint).fireBreakpointChanged(); |
| } |
| } |