/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.groovy.debug.core.breakpoints; import java.text.MessageFormat; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IValue; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.dom.Message; import org.eclipse.jdt.debug.core.IJavaBreakpoint; import org.eclipse.jdt.debug.core.IJavaBreakpointListener; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.core.IJavaValue; import org.eclipse.jdt.debug.eval.IEvaluationListener; import org.eclipse.jdt.debug.eval.IEvaluationResult; import org.eclipse.jdt.groovy.core.util.ReflectionUtils; import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; import org.eclipse.jdt.internal.debug.core.breakpoints.JDIDebugBreakpointMessages; import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint; import org.eclipse.jdt.internal.debug.core.model.JDIThread; import org.grails.ide.eclipse.groovy.debug.core.GroovyDebugCoreActivator; import org.grails.ide.eclipse.groovy.debug.core.evaluation.GroovyJDIEvaluator; import org.grails.ide.eclipse.groovy.debug.core.evaluation.JDITargetDelegate; import com.sun.jdi.VMDisconnectedException; /** * * @author Andrew Eisenberg * @since 2.6.1 */ public class GroovyConditionalBreakpointHandler { /** * Listens for evaluation completion for condition evaluation. * If an evaluation evaluates <code>true</code> or has an error, this breakpoint * will suspend the thread in which the breakpoint was hit. * If the evaluation returns <code>false</code>, the thread is resumed. */ class EvaluationListener implements IEvaluationListener { /** * Lock for synchronizing evaluation */ private Object fLock = new Object(); /** * The breakpoint that was hit */ private JavaLineBreakpoint fBreakpoint; /** * Result of the vote */ private int fVote; EvaluationListener(JavaLineBreakpoint breakpoint) { fBreakpoint = breakpoint; } public void evaluationComplete(IEvaluationResult result) { fVote = determineVote(result); synchronized (fLock) { fLock.notifyAll(); } } /** * Processes the result to determine whether to suspend or resume. * * @param result evaluation result * @return vote */ private int determineVote(IEvaluationResult result) { if (result.isTerminated()) { // indicates the user terminated the evaluation return IJavaBreakpointListener.SUSPEND; } JDIThread thread= (JDIThread)result.getThread(); if (result.hasErrors()) { DebugException exception= result.getException(); Throwable wrappedException= exception.getStatus().getException(); if (wrappedException instanceof VMDisconnectedException) { // VM terminated/disconnected during evaluation return IJavaBreakpointListener.DONT_SUSPEND; } else { fireConditionHasRuntimeErrors(fBreakpoint, exception); return IJavaBreakpointListener.SUSPEND; } } else { try { IValue value= result.getValue(); if (fBreakpoint.isConditionSuspendOnTrue()) { try { Boolean b = JDITargetDelegate.convertToBoolean((IJavaValue) value); return b ? IJavaBreakpointListener.SUSPEND : IJavaBreakpointListener.DONT_SUSPEND; } catch (DebugException e) { // result was not boolean fireConditionHasRuntimeErrors(fBreakpoint, new DebugException( new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), MessageFormat.format(JDIDebugBreakpointMessages.ConditionalBreakpointHandler_1, new String[]{value.getReferenceTypeName()})))); return IJavaBreakpointListener.SUSPEND; } } else { IDebugTarget debugTarget= thread.getDebugTarget(); IValue lastValue= setCurrentConditionValue(value, debugTarget); if (!value.equals(lastValue)) { return IJavaBreakpointListener.SUSPEND; } else { return IJavaBreakpointListener.DONT_SUSPEND; } } } catch (DebugException e) { // Suspend when an error occurs JDIDebugPlugin.log(e); return IJavaBreakpointListener.SUSPEND; } } } /** * @param value * @param debugTarget * @return */ protected IValue setCurrentConditionValue(IValue value, IDebugTarget debugTarget) { return (IValue) ReflectionUtils.executePrivateMethod( JavaLineBreakpoint.class, "setCurrentConditionValue", new Class[] { IDebugTarget.class, IValue.class }, fBreakpoint, new Object[] { debugTarget, value }); } /** * Result of the conditional expression evaluation - to resume or not resume, that * is the question. * * @return vote result */ int getVote() { return fVote; } /** * Returns the lock object to synchronize this evaluation. * * @return lock object */ Object getLock() { return fLock; } } /** * Whether the condition had compile or runtime errors */ private boolean fHasErrors = false; /* * Closely follow the logic of ConditionalBreakpointListener.breakpointHit() */ public int conditionalBreakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) { if (breakpoint instanceof IJavaLineBreakpoint) { JavaLineBreakpoint lineBreakpoint = (JavaLineBreakpoint) breakpoint; try { final String condition= lineBreakpoint.getCondition(); if (condition == null) { return IJavaBreakpointListener.SUSPEND; } EvaluationListener listener= new EvaluationListener(lineBreakpoint); IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame(); IJavaProject project= getJavaProject(lineBreakpoint, frame); if (project == null) { fireConditionHasErrors(lineBreakpoint, new Message[]{new Message(JDIDebugBreakpointMessages.JavaLineBreakpoint_Unable_to_compile_conditional_breakpoint___missing_Java_project_context__1, -1)}); return IJavaBreakpointListener.SUSPEND; } IJavaDebugTarget target = (IJavaDebugTarget) thread.getDebugTarget(); GroovyJDIEvaluator evaluator = new GroovyJDIEvaluator(project, target); Object lock = listener.getLock(); synchronized (lock) { evaluator.evaluate(lineBreakpoint.getCondition(), null, frame, listener, DebugEvent.EVALUATION_IMPLICIT, false); // TODO: timeout? try { lock.wait(); } catch (InterruptedException e) { fireConditionHasRuntimeErrors(lineBreakpoint, new DebugException( new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), JDIDebugBreakpointMessages.ConditionalBreakpointHandler_0, e))); return IJavaBreakpointListener.SUSPEND; } } return listener.getVote(); } catch (CoreException e) { DebugException de = null; if (e instanceof DebugException) { de = (DebugException) e; } else { de = new DebugException(e.getStatus()); } fireConditionHasRuntimeErrors(lineBreakpoint, de); } } return IJavaBreakpointListener.SUSPEND; } /** * @param lineBreakpoint * @param frame * @return */ protected IJavaProject getJavaProject(JavaLineBreakpoint lineBreakpoint, IJavaStackFrame frame) { return (IJavaProject) ReflectionUtils.executePrivateMethod( JavaLineBreakpoint.class, "getJavaProject", new Class[] { IJavaStackFrame.class }, lineBreakpoint, new Object[] { frame }); } private void fireConditionHasRuntimeErrors(IJavaLineBreakpoint breakpoint, DebugException exception) { fHasErrors = true; GroovyDebugCoreActivator.log(exception); JDIDebugPlugin.getDefault().fireBreakpointHasRuntimeException(breakpoint, exception); } /** * Notifies listeners that a conditional breakpoint expression has been * compiled that contains errors */ private void fireConditionHasErrors(IJavaLineBreakpoint breakpoint, Message[] messages) { fHasErrors = true; JDIDebugPlugin.getDefault().fireBreakpointHasCompilationErrors(breakpoint, messages); } /** * Returns whether errors were encountered when evaluating the condition (compilation or runtime). * * @return whether errors were encountered when evaluating the condition */ public boolean hasErrors() { return fHasErrors; } }