Bug 260910 - Ability to contribute actions to be run whenever a java breakpoint is hit
diff --git a/org.eclipse.jdt.debug.tests/plugin.xml b/org.eclipse.jdt.debug.tests/plugin.xml
index 7e92c3d..1700cf9 100644
--- a/org.eclipse.jdt.debug.tests/plugin.xml
+++ b/org.eclipse.jdt.debug.tests/plugin.xml
@@ -487,5 +487,21 @@
         </run>
      </filesystem>
   </extension>
+  <extension
+        point="org.eclipse.jdt.debug.breakpointListeners">
+     <breakpointListener
+           class="org.eclipse.jdt.debug.testplugin.EvalualtionBreakpointListener"
+           id="org.eclipse.jdt.debug.tests.evalListener">
+     </breakpointListener>
+     <breakpointListener
+           class="org.eclipse.jdt.debug.testplugin.ResumeBreakpointListener"
+           id="org.eclipse.jdt.debug.tests.resumeListener">
+     </breakpointListener>
+     <breakpointListener
+           class="org.eclipse.jdt.debug.testplugin.GlobalBreakpointListener"
+           filter="*"
+           id="org.eclipse.jdt.debug.tests.listener.global">
+     </breakpointListener>
+  </extension>
 
 </plugin>
diff --git a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/EvalualtionBreakpointListener.java b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/EvalualtionBreakpointListener.java
new file mode 100644
index 0000000..9dd75d7
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/EvalualtionBreakpointListener.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.testplugin;
+
+import junit.framework.AssertionFailedError;
+
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.DebugException;
+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.IJavaType;
+import org.eclipse.jdt.debug.eval.EvaluationManager;
+import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
+import org.eclipse.jdt.debug.eval.IEvaluationListener;
+import org.eclipse.jdt.debug.eval.IEvaluationResult;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+
+/**
+ * Test for breakpoint listener extension. Performs an evaluation while voting
+ * whether to resume a thread.
+ */
+public class EvalualtionBreakpointListener implements IJavaBreakpointListener {
+	
+	/**
+	 * How to vote when hit
+	 */
+	public static int VOTE = IJavaBreakpointListener.DONT_CARE;
+	
+	/**
+	 * Expression to evaluate when hit
+	 */
+	public static String EXPRESSION;
+	
+	/**
+	 * Project to compile expression in
+	 */
+	public static IJavaProject PROJECT;
+	
+	/**
+	 * Evaluation result
+	 */
+	public static IEvaluationResult RESULT;
+
+	/**
+	 * Constructs a breakpoint listener to evaluate an expression when a breakpoint is hit.
+	 */
+	public EvalualtionBreakpointListener() {
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#addingBreakpoint(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+	 */
+	public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointHasCompilationErrors(org.eclipse.jdt.debug.core.IJavaLineBreakpoint, org.eclipse.jdt.core.dom.Message[])
+	 */
+	public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointHasRuntimeException(org.eclipse.jdt.debug.core.IJavaLineBreakpoint, org.eclipse.debug.core.DebugException)
+	 */
+	public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointHit(org.eclipse.jdt.debug.core.IJavaThread, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+	 */
+	public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
+		final Object lock = new Object();
+		RESULT = null;
+		IAstEvaluationEngine engine = EvaluationManager.newAstEvaluationEngine(PROJECT, (IJavaDebugTarget) thread.getDebugTarget());
+		IEvaluationListener listener = new IEvaluationListener(){
+			public void evaluationComplete(IEvaluationResult result) {
+				RESULT = result;
+				synchronized (lock) {
+					lock.notifyAll();
+				}
+			}
+		};
+		try {
+			synchronized (lock) {
+				engine.evaluate(EXPRESSION, (IJavaStackFrame) thread.getTopStackFrame(), listener, DebugEvent.EVALUATION_IMPLICIT, false);
+				lock.wait(AbstractDebugTest.DEFAULT_TIMEOUT);
+			}
+		} catch (DebugException e) {
+			throw new AssertionFailedError(e.getStatus().getMessage());
+		} catch (InterruptedException e) {
+			throw new AssertionFailedError(e.getMessage());
+		}
+		return VOTE;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointInstalled(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+	 */
+	public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointRemoved(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+	 */
+	public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#installingBreakpoint(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint, org.eclipse.jdt.debug.core.IJavaType)
+	 */
+	public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
+		return IJavaBreakpointListener.DONT_CARE;
+	}
+	
+}
diff --git a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/GlobalBreakpointListener.java b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/GlobalBreakpointListener.java
new file mode 100644
index 0000000..be167bd
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/GlobalBreakpointListener.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.testplugin;
+
+import org.eclipse.debug.core.DebugException;
+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.IJavaThread;
+import org.eclipse.jdt.debug.core.IJavaType;
+
+/**
+ * Listens to all breakpoint notifications.
+ */
+public class GlobalBreakpointListener implements IJavaBreakpointListener {
+	
+	public static IJavaBreakpoint ADDED;
+	public static IJavaBreakpoint HIT;
+	public static IJavaBreakpoint INSTALLED;
+	public static IJavaBreakpoint REMOVED;
+	public static IJavaBreakpoint INSTALLING;
+	
+	public static void clear() {
+		ADDED = null;
+		HIT = null;
+		INSTALLED = null;
+		REMOVED = null;
+		INSTALLING = null;
+	}
+
+	public GlobalBreakpointListener() {
+	}
+
+	public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+		ADDED = breakpoint;
+	}
+
+	public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {
+	}
+
+	public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
+	}
+
+	public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
+		HIT = breakpoint;
+		return DONT_CARE;
+	}
+
+	public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+		INSTALLED = breakpoint;
+	}
+
+	public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+		REMOVED = breakpoint;
+	}
+
+	public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
+		INSTALLING = breakpoint;
+		return DONT_CARE;
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java
new file mode 100644
index 0000000..f5454a2
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.testplugin;
+
+import org.eclipse.debug.core.DebugException;
+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.IJavaThread;
+import org.eclipse.jdt.debug.core.IJavaType;
+
+public class ResumeBreakpointListener implements IJavaBreakpointListener {
+	
+	public static boolean WAS_HIT = false; 
+
+	public ResumeBreakpointListener() {
+	}
+
+	public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {
+	}
+
+	public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
+	}
+
+	public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
+		WAS_HIT = true;
+		return IJavaBreakpointListener.DONT_SUSPEND;
+	}
+
+	public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
+		return IJavaBreakpointListener.DONT_CARE;
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
index 9406c37..226c613 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
@@ -1458,6 +1458,23 @@
 		assertNotNull("Program did not suspend.", suspendee); //$NON-NLS-1$
 		return (IJavaThread) suspendee;
 	}
+	
+	/**
+	 * Performs a step over in the given stack frame and returns when a breakpoint is hit.
+	 * 
+	 * @param frame stack frame to step in
+	 */
+	protected IJavaThread stepOverToBreakpoint(IJavaStackFrame frame) throws Exception {
+		DebugEventWaiter waiter= new DebugElementKindEventDetailWaiter(DebugEvent.SUSPEND, IJavaThread.class, DebugEvent.BREAKPOINT);
+		waiter.setTimeout(DEFAULT_TIMEOUT);
+		
+		frame.stepOver();
+		
+		Object suspendee= waiter.waitForEvent();
+		setEventSet(waiter.getEventSet());
+		assertNotNull("Program did not suspend.", suspendee); //$NON-NLS-1$
+		return (IJavaThread) suspendee;
+	}	
 
 	/**
 	 * Performs a step into in the given stack frame and returns when complete.
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsTests.java
index c4c123a..b901e6c 100755
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ConditionalBreakpointsTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -10,6 +10,8 @@
  *******************************************************************************/
 package org.eclipse.jdt.debug.tests.breakpoints;
 
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.debug.core.model.IStackFrame;
 import org.eclipse.debug.core.model.IVariable;
 import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
 import org.eclipse.jdt.debug.core.IJavaPrimitiveValue;
@@ -147,4 +149,68 @@
 		}		
 	}
 
+	/**
+	 * Tests a breakpoint condition is not evaluated when it coincides with a step end.
+	 * 
+	 * @throws Exception
+	 */
+	public void testSkipConditionOnStep() throws Exception {
+		String typeName = "HitCountLooper";
+		IJavaLineBreakpoint bp = createLineBreakpoint(16, typeName);
+		IJavaLineBreakpoint bp2 = createConditionalLineBreakpoint(17, typeName, "i = 3; return true;", true);
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, bp);
+			// step from 16 to 17, breakpoint condition should not evaluate
+			thread = stepOver((IJavaStackFrame) thread.getTopStackFrame());
+			IJavaStackFrame frame = (IJavaStackFrame)thread.getTopStackFrame();
+			IVariable var = findVariable(frame, "i");
+			assertNotNull("Could not find variable 'i'", var);
+			
+			IJavaPrimitiveValue value = (IJavaPrimitiveValue)var.getValue();
+			assertNotNull("variable 'i' has no value", value);
+			int iValue = value.getIntValue();
+			assertTrue("value of 'i' should be '3', but was " + iValue, iValue == 0);
+			
+			// breakpoint should still be available from thread, even though not eval'd
+			IBreakpoint[] breakpoints = thread.getBreakpoints();
+			assertEquals("Wrong number of breakpoints", 1, breakpoints.length);
+			assertEquals("Wrong breakpoint", bp2, breakpoints[0]);
+			
+			bp.delete();
+			bp2.delete();
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}		
+	}	
+	
+	/**
+	 * Tests that a thread can be suspended when executing a long-running condition.
+	 * 
+	 * @throws Exception
+	 */
+	public void testSuspendLongRunningCondition() throws Exception {
+		String typeName = "MethodLoop";
+		IJavaLineBreakpoint first = createLineBreakpoint(19, typeName);
+		createConditionalLineBreakpoint(29, typeName, "for (int x = 0; x < 1000; x++) { System.out.println(x);} Thread.sleep(200); return true;", true);
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, first);
+			IStackFrame top = thread.getTopStackFrame();
+			assertNotNull("Missing top frame", top);
+			thread.resume();
+			Thread.sleep(100);
+			thread.suspend();
+			assertTrue("Thread should be suspended", thread.isSuspended());
+			IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame();
+			assertNotNull("Missing top frame", frame);
+			assertEquals("Wrong location", "calculateSum", frame.getName());
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}	
+	}
 }
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/JavaBreakpointListenerTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/JavaBreakpointListenerTests.java
index 0566c71..d635fad 100755
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/JavaBreakpointListenerTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/JavaBreakpointListenerTests.java
@@ -10,7 +10,13 @@
  *******************************************************************************/
 package org.eclipse.jdt.debug.tests.breakpoints;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.model.IStackFrame;
+import org.eclipse.debug.core.model.IValue;
+import org.eclipse.debug.core.model.IVariable;
 import org.eclipse.jdt.core.dom.Message;
 import org.eclipse.jdt.debug.core.IJavaBreakpoint;
 import org.eclipse.jdt.debug.core.IJavaBreakpointListener;
@@ -18,9 +24,16 @@
 import org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint;
 import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
 import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
+import org.eclipse.jdt.debug.core.IJavaPrimitiveValue;
+import org.eclipse.jdt.debug.core.IJavaStackFrame;
 import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.core.IJavaType;
+import org.eclipse.jdt.debug.core.IJavaValue;
 import org.eclipse.jdt.debug.core.JDIDebugModel;
+import org.eclipse.jdt.debug.eval.IEvaluationResult;
+import org.eclipse.jdt.debug.testplugin.EvalualtionBreakpointListener;
+import org.eclipse.jdt.debug.testplugin.GlobalBreakpointListener;
+import org.eclipse.jdt.debug.testplugin.ResumeBreakpointListener;
 import org.eclipse.jdt.debug.tests.AbstractDebugTest;
 
 /**
@@ -139,6 +152,39 @@
 			return DONT_CARE;
 		}
 	}
+	
+	/**
+	 * Collects hit breakpoints.
+	 */
+	class Collector implements IJavaBreakpointListener {
+		
+		public List HIT = new ArrayList();
+
+		public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+		}
+
+		public void breakpointHasCompilationErrors( IJavaLineBreakpoint breakpoint, Message[] errors) {
+		}
+
+		public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
+		}
+
+		public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
+			HIT.add(breakpoint);
+			return DONT_CARE;
+		}
+
+		public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {			
+		}
+
+		public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+		}
+
+		public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
+			return DONT_CARE;
+		}
+		
+	}
 			
 	/**
 	 * Constructor
@@ -693,4 +739,305 @@
 			}
 	}
 
+	/**
+	 * Tests a breakpoint listener extension.
+	 */
+	public void testEvalListenerExtension() throws Exception {
+		String typeName = "HitCountLooper";
+		IJavaLineBreakpoint bp = createLineBreakpoint(16, typeName);
+		bp.addBreakpointListener("org.eclipse.jdt.debug.tests.evalListener");
+		EvalualtionBreakpointListener.PROJECT = getJavaProject();
+		EvalualtionBreakpointListener.EXPRESSION = "return new Integer(i);";
+		EvalualtionBreakpointListener.VOTE = IJavaBreakpointListener.SUSPEND;
+			
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, bp);
+			IEvaluationResult result = EvalualtionBreakpointListener.RESULT;
+			assertNotNull("Missing eval result", result);
+			assertFalse("Should be no errors", result.hasErrors());
+			IJavaValue value = result.getValue();
+			assertEquals("Wrong result type", "java.lang.Integer", value.getReferenceTypeName());
+			IVariable[] variables = value.getVariables();
+			for (int i = 0; i < variables.length; i++) {
+				IVariable variable = variables[i];
+				if (variable.getName().equals("value")) {
+					IValue iValue = variable.getValue();
+					assertTrue("Should be an int", iValue.getReferenceTypeName().equals("int"));
+					assertTrue("Should be a primitive", iValue instanceof IJavaPrimitiveValue);
+					int intValue = ((IJavaPrimitiveValue)iValue).getIntValue();
+					assertEquals("Wrong value", 0, intValue);
+					break;
+				}
+			}
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}			
+	}
+	
+	/**
+	 * Tests that a step end that lands on a breakpoint listener that votes to resume
+	 * results in the step completing and suspending.
+	 * 
+	 * @throws Exception
+	 */
+	public void testStepEndResumeVote() throws Exception {
+		String typeName = "HitCountLooper";
+		IJavaLineBreakpoint first = createLineBreakpoint(16, typeName);
+		IJavaLineBreakpoint second = createLineBreakpoint(17, typeName);
+		second.addBreakpointListener("org.eclipse.jdt.debug.tests.resumeListener");
+			
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, first);
+			thread = stepOver((IJavaStackFrame) thread.getTopStackFrame());
+			assertTrue("Listener not notified", ResumeBreakpointListener.WAS_HIT);
+			assertTrue("Thread should be suspended", thread.isSuspended());
+			assertNotNull("Top frame should be available", thread.getTopStackFrame());
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}					
+	}
+	
+	/**
+	 * Test that a step over hitting a breakpoint deeper up the stack with a listener
+	 * can perform an evaluation and resume to complete the step.
+	 * 
+	 * @throws Exception
+	 */
+	public void testStepOverHitsNestedEvaluationHandlerResume() throws Exception {
+		String typeName = "MethodLoop";
+		// breakpoint on line 24 is where the step is initiated from
+		IJavaLineBreakpoint first = createLineBreakpoint(24, typeName);
+		// second breakpoint is where the evaluation is performed with a resume vote
+		IJavaLineBreakpoint second = createLineBreakpoint(29, typeName);
+		second.addBreakpointListener("org.eclipse.jdt.debug.tests.evalListener");
+		EvalualtionBreakpointListener.PROJECT = getJavaProject();
+		EvalualtionBreakpointListener.EXPRESSION = "return new Integer(sum);";
+		EvalualtionBreakpointListener.VOTE = IJavaBreakpointListener.DONT_SUSPEND;
+		EvalualtionBreakpointListener.RESULT = null;
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, first);
+			IStackFrame top = thread.getTopStackFrame();
+			assertNotNull("Missing top frame", top);
+			thread = stepOver((IJavaStackFrame) top);
+			assertTrue("Thread should be suspended", thread.isSuspended());
+			assertEquals("Should be in frame where step originated", top, thread.getTopStackFrame());
+			IEvaluationResult result = EvalualtionBreakpointListener.RESULT;
+			assertNotNull("Missing eval result", result);
+			assertFalse("Should be no errors", result.hasErrors());
+			if (result.getException() != null) {
+				result.getException().printStackTrace();
+			}
+			IJavaValue value = result.getValue();
+			assertEquals("Wrong result type", "java.lang.Integer", value.getReferenceTypeName());
+			IVariable[] variables = value.getVariables();
+			for (int i = 0; i < variables.length; i++) {
+				IVariable variable = variables[i];
+				if (variable.getName().equals("value")) {
+					IValue iValue = variable.getValue();
+					assertTrue("Should be an int", iValue.getReferenceTypeName().equals("int"));
+					assertTrue("Should be a primitive", iValue instanceof IJavaPrimitiveValue);
+					int intValue = ((IJavaPrimitiveValue)iValue).getIntValue();
+					assertEquals("Wrong value", 0, intValue);
+					break;
+				}
+			}
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+	
+	/**
+	 * Test that a step over hitting a breakpoint deeper up the stack with a listener
+	 * can perform an evaluation and suspend to abort the step.
+	 * 
+	 * @throws Exception
+	 */
+	public void testStepOverHitsNestedEvaluationHandlerSuspend() throws Exception {
+		String typeName = "MethodLoop";
+		// breakpoint on line 24 is where the step is initiated from
+		IJavaLineBreakpoint first = createLineBreakpoint(24, typeName);
+		// second breakpoint is where the evaluation is performed with a resume vote
+		IJavaLineBreakpoint second = createLineBreakpoint(29, typeName);
+		second.addBreakpointListener("org.eclipse.jdt.debug.tests.evalListener");
+		EvalualtionBreakpointListener.PROJECT = getJavaProject();
+		EvalualtionBreakpointListener.EXPRESSION = "return new Integer(sum);";
+		EvalualtionBreakpointListener.VOTE = IJavaBreakpointListener.SUSPEND;
+		EvalualtionBreakpointListener.RESULT = null;
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, first);
+			IStackFrame top = thread.getTopStackFrame();
+			assertNotNull("Missing top frame", top);
+			thread = stepOverToBreakpoint((IJavaStackFrame) top);
+			assertTrue("Thread should be suspended", thread.isSuspended());
+			IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame();
+			assertNotNull("Missin top frame", frame);
+			assertEquals("Wrong location", "calculateSum", frame.getName());
+			IEvaluationResult result = EvalualtionBreakpointListener.RESULT;
+			assertNotNull("Missing eval result", result);
+			assertFalse("Should be no errors", result.hasErrors());
+			if (result.getException() != null) {
+				result.getException().printStackTrace();
+			}
+			IJavaValue value = result.getValue();
+			assertEquals("Wrong result type", "java.lang.Integer", value.getReferenceTypeName());
+			IVariable[] variables = value.getVariables();
+			for (int i = 0; i < variables.length; i++) {
+				IVariable variable = variables[i];
+				if (variable.getName().equals("value")) {
+					IValue iValue = variable.getValue();
+					assertTrue("Should be an int", iValue.getReferenceTypeName().equals("int"));
+					assertTrue("Should be a primitive", iValue instanceof IJavaPrimitiveValue);
+					int intValue = ((IJavaPrimitiveValue)iValue).getIntValue();
+					assertEquals("Wrong value", 0, intValue);
+					break;
+				}
+			}
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}		
+	}	
+	
+	/**
+	 * Suspends an evaluation. Ensures we're returned to the proper top frame.
+	 * 
+	 * @throws Exception
+	 */
+	public void testSuspendEvaluation() throws Exception {
+		String typeName = "MethodLoop";
+		IJavaLineBreakpoint first = createLineBreakpoint(19, typeName);
+		IJavaLineBreakpoint second = createLineBreakpoint(29, typeName);
+		second.addBreakpointListener("org.eclipse.jdt.debug.tests.evalListener");
+		EvalualtionBreakpointListener.PROJECT = getJavaProject();
+		EvalualtionBreakpointListener.EXPRESSION = "for (int x = 0; x < 1000; x++) { System.out.println(x);} Thread.sleep(200);";
+		EvalualtionBreakpointListener.VOTE = IJavaBreakpointListener.DONT_SUSPEND;
+		EvalualtionBreakpointListener.RESULT = null;
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, first);
+			IStackFrame top = thread.getTopStackFrame();
+			assertNotNull("Missing top frame", top);
+			thread.resume();
+			Thread.sleep(100);
+			thread.suspend();
+			assertTrue("Thread should be suspended", thread.isSuspended());
+			IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame();
+			assertNotNull("Missing top frame", frame);
+			assertEquals("Wrong location", "calculateSum", frame.getName());
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}				
+	}
+	
+	/**
+	 * Test that a global listener gets notifications.
+	 * 
+	 * @throws Exception
+	 */
+	public void testGlobalListener() throws Exception {
+		GlobalBreakpointListener.clear();
+		String typeName = "HitCountLooper";
+		IJavaLineBreakpoint bp = createLineBreakpoint(16, typeName);
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, bp);
+
+			assertEquals("Should be ADDED", bp, GlobalBreakpointListener.ADDED);
+			assertEquals("Should be INSTALLING", bp, GlobalBreakpointListener.INSTALLING);
+			assertEquals("Should be INSTALLED", bp, GlobalBreakpointListener.INSTALLED);
+			assertEquals("Should be HIT", bp, GlobalBreakpointListener.HIT);
+			assertNull("Should not be REMOVED", GlobalBreakpointListener.REMOVED);
+						
+			bp.delete();
+			assertEquals("Should be REMOVED", bp, GlobalBreakpointListener.REMOVED);
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}				
+	}
+	
+	/**
+	 * Tests that breakpoint listeners are only notified when condition is true.
+	 * 
+	 * @throws Exception
+	 */
+	public void testListenersOnConditionalBreakpoint() throws Exception {
+		String typeName = "HitCountLooper";
+		Collector collector = new Collector();
+		JDIDebugModel.addJavaBreakpointListener(collector);
+		IJavaLineBreakpoint bp = createConditionalLineBreakpoint(16, typeName, "return false;", true);
+		IJavaLineBreakpoint second = createConditionalLineBreakpoint(17, typeName, "i == 3", true);
+		
+		IJavaThread thread= null;
+		try {
+			thread= launchToLineBreakpoint(typeName, second);
+			assertEquals("Wrong number of breakpoints hit", 1, collector.HIT.size());
+			assertTrue("Wrong breakpoint hit", collector.HIT.contains(second));
+			assertFalse("Wrong breakpoint hit", collector.HIT.contains(bp));
+		} finally {
+			JDIDebugModel.removeJavaBreakpointListener(collector);
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
+	/**
+	 * Tests addition and removal of breakpoint listeners to a breakpoint.
+	 * 
+	 * @throws Exception
+	 */
+	public void testAddRemoveListeners() throws Exception {
+		String typeName = "HitCountLooper";
+		IJavaLineBreakpoint bp = createLineBreakpoint(16, typeName);
+		
+		String[] listeners = bp.getBreakpointListeners();
+		assertEquals(0, listeners.length);
+		
+		bp.addBreakpointListener("a");
+		listeners = bp.getBreakpointListeners();
+		assertEquals(1, listeners.length);
+		assertEquals("a", listeners[0]);
+		
+		bp.addBreakpointListener("b");
+		listeners = bp.getBreakpointListeners();
+		assertEquals(2, listeners.length);
+		assertEquals("a", listeners[0]);
+		assertEquals("b", listeners[1]);
+		
+		bp.addBreakpointListener("c");
+		listeners = bp.getBreakpointListeners();
+		assertEquals(3, listeners.length);
+		assertEquals("a", listeners[0]);
+		assertEquals("b", listeners[1]);
+		assertEquals("c", listeners[2]);
+		
+		bp.removeBreakpointListener("a");
+		listeners = bp.getBreakpointListeners();
+		assertEquals(2, listeners.length);
+		assertEquals("b", listeners[0]);
+		assertEquals("c", listeners[1]);
+		
+		bp.removeBreakpointListener("c");
+		listeners = bp.getBreakpointListeners();
+		assertEquals(1, listeners.length);
+		assertEquals("b", listeners[0]);
+		
+		bp.removeBreakpointListener("b");
+		listeners = bp.getBreakpointListeners();
+		assertEquals(0, listeners.length);
+
+	}
+	
 }
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java
index 7ed810e..ea8d8d4 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -318,10 +318,14 @@
 		} else if (thread.isStepping()) {
 			key.append("stepping"); //$NON-NLS-1$
 			args = new String[] {thread.getName()};
+		} else if ((thread instanceof JDIThread && ((JDIThread)thread).isSuspendVoteInProgress()) && !thread.getDebugTarget().isSuspended()) {
+			// show running when listener notification is in progress
+			key.append("running"); //$NON-NLS-1$
+			args = new String[] {thread.getName()};
 		} else if (thread.isPerformingEvaluation() && breakpoints.length == 0) {
 			key.append("evaluating"); //$NON-NLS-1$
 			args = new String[] {thread.getName()};
-		} else if (!thread.isSuspended() || (thread instanceof JDIThread && ((JDIThread)thread).isSuspendedQuiet())) {
+		} else if (!thread.isSuspended()) {
 			key.append("running"); //$NON-NLS-1$
 			args = new String[] {thread.getName()};
 		} else {
@@ -663,6 +667,15 @@
 			if (item instanceof IJavaBreakpoint) {
 				return getBreakpointImage((IJavaBreakpoint)item);
 			}
+			if (item instanceof JDIThread) {
+				JDIThread jt = (JDIThread) item;
+				if (jt.getDebugTarget().isSuspended()) {
+					return DebugUITools.getImage(IDebugUIConstants.IMG_OBJS_THREAD_SUSPENDED);
+				}
+				if (jt.isSuspendVoteInProgress()) {
+					return DebugUITools.getImage(IDebugUIConstants.IMG_OBJS_THREAD_RUNNING);
+				}
+			}
 			if (item instanceof IJavaStackFrame || item instanceof IJavaThread || item instanceof IJavaDebugTarget) {
 				return getDebugElementImage(item);
 			}
@@ -912,7 +925,8 @@
 		ImageDescriptor image= null;
 		if (element instanceof IJavaThread) {
 			IJavaThread thread = (IJavaThread)element;
-			if (thread.isSuspended() && !thread.isPerformingEvaluation()) {
+			// image also needs to handle suspended quiet
+			if (thread.isSuspended() && !thread.isPerformingEvaluation() && !(thread instanceof JDIThread && ((JDIThread)thread).isSuspendVoteInProgress())) {
 				image= DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_OBJS_THREAD_SUSPENDED);
 			} else if (thread.isTerminated()) {
 				image= DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_OBJS_THREAD_TERMINATED);
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java
index 6f62145..8000801 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/monitors/JavaThreadContentProvider.java
@@ -68,6 +68,14 @@
 	
 	protected Object[] getChildren(IJavaThread thread) {
 		try {
+			if (thread instanceof JDIThread) {
+				JDIThread jThread = (JDIThread) thread;
+				if (!jThread.getDebugTarget().isSuspended() ) {
+					if (jThread.isSuspendVoteInProgress()) {
+						return EMPTY;
+					}
+				}
+			}
 			IStackFrame[] frames = thread.getStackFrames();
 			if (!isDisplayMonitors()) {
 				return frames;
@@ -109,6 +117,14 @@
 	 * @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#hasChildren(java.lang.Object, org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, org.eclipse.core.runtime.IProgressMonitor)
 	 */
 	protected boolean hasChildren(Object element, IPresentationContext context, IViewerUpdate monitor) throws CoreException {
+		if (element instanceof JDIThread) {
+			JDIThread jThread = (JDIThread) element;
+			if (!jThread.getDebugTarget().isSuspended()) {
+				if (jThread.isSuspendVoteInProgress()) {
+					return false;
+				}
+			}
+		}
 		return ((IJavaThread)element).hasStackFrames() ||
 			(isDisplayMonitors() && ((IJavaThread)element).hasOwnedMonitors());
 	}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/threadgroups/JavaThreadEventHandler.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/threadgroups/JavaThreadEventHandler.java
index 97fccd4..0295d5d 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/threadgroups/JavaThreadEventHandler.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/threadgroups/JavaThreadEventHandler.java
@@ -30,6 +30,7 @@
 import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.core.IJavaThreadGroup;
 import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants;
+import org.eclipse.jdt.internal.debug.core.model.JDIThread;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPreferenceInitializer;
 import org.eclipse.jdt.internal.debug.ui.monitors.JavaElementContentProvider;
@@ -268,4 +269,13 @@
 		}
 	}	
 
+	/**
+	 * Do not update for quiet resume/suspend
+	 */
+	protected void handleOther(DebugEvent event) {
+		if (event.getDetail() == JDIThread.SUSPEND_QUIET || event.getDetail() == JDIThread.RESUME_QUIET) {
+			return;
+		}
+		super.handleOther(event);
+	}
 }
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IEvaluationResult.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IEvaluationResult.java
index cf68a8a..0cf789d 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IEvaluationResult.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IEvaluationResult.java
@@ -33,7 +33,10 @@
 	 * associated evaluation failed. If
 	 * the associated evaluation failed, there will
 	 * be problems, or an exception in this result.
-	 *
+	 * <p>
+	 * Since 3.5, this method can also return null if
+	 * the evaluation was terminated before it completed.
+	 * </p>
 	 * @return the resulting value, possibly
 	 * <code>null</code>
 	 */
@@ -103,4 +106,12 @@
 	 *  original snippet
 	 */
 	public IEvaluationEngine getEvaluationEngine();	
+	
+	/**
+	 * Returns whether this evaluation was terminated before it completed.
+	 * 
+	 * @return whether terminated.
+	 * @since 3.5
+	 */
+	public boolean isTerminated();
 }
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/EvaluationResult.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/EvaluationResult.java
index 62820ba..3b234c0 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/EvaluationResult.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/EvaluationResult.java
@@ -60,6 +60,11 @@
 	 * List of <code>String</code>s describing compilation problems.
 	 */
 	private List fErrors;
+	
+	/**
+	 * Whether the evaluation was terminated.
+	 */
+	private boolean fTerminated = false;
 
 	/**
 	 * Constructs a new evaluation result for the given
@@ -190,5 +195,21 @@
 	public void addError(String message) {
 		fErrors.add(message);
 	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.eval.IEvaluationResult#isTerminated()
+	 */
+	public boolean isTerminated() {
+		return fTerminated;
+	}
+
+	/**
+	 * Sets whether terminated.
+	 * 
+	 * @param terminated whether terminated
+	 */
+	public void setTerminated(boolean terminated) {
+		fTerminated = terminated;
+	}
 }
 
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java
index 0955f1c..f0719ed 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java
@@ -469,6 +469,7 @@
 			class EvaluationRunnable implements IEvaluationRunnable, ITerminate {
 				
 				CoreException fException;
+				boolean fTerminated = false;
 				
 				public void run(IJavaThread jt, IProgressMonitor pm) {
 					EventFilter filter = new EventFilter();
@@ -493,6 +494,7 @@
 					}
 				}
 				public void terminate() {
+					fTerminated = true;
 					interpreter.stop();
 				}
 				public boolean canTerminate() {
@@ -521,6 +523,7 @@
 				exception = er.getException();
 			}
             
+			result.setTerminated(er.fTerminated);
 			if (exception != null) {
 			    if (exception instanceof DebugException) {
 			        result.setException((DebugException)exception);
diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/Interpreter.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/Interpreter.java
index f240fe7..28bfd82 100644
--- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/Interpreter.java
+++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/Interpreter.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -18,15 +18,20 @@
 import java.util.Stack;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.debug.core.model.IVariable;
 import org.eclipse.jdt.debug.core.IJavaObject;
 import org.eclipse.jdt.debug.core.IJavaType;
 import org.eclipse.jdt.debug.core.IJavaValue;
 import org.eclipse.jdt.debug.core.IJavaVariable;
+import org.eclipse.jdt.debug.core.JDIDebugModel;
 import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
 import org.eclipse.jdt.internal.debug.eval.ast.instructions.Instruction;
 import org.eclipse.jdt.internal.debug.eval.ast.instructions.InstructionSequence;
 
+import com.sun.jdi.VMDisconnectedException;
+
 public class Interpreter {
 	private Instruction[] fInstructions;
 	private int fInstructionCounter;
@@ -61,6 +66,8 @@
 				instruction.execute();
 				instruction.setInterpreter(null);
 			}
+		} catch (VMDisconnectedException e) {
+			throw new CoreException(new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), e.getMessage(), e));
 		} finally {
 			releaseObjects();
 		}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java
index 8d51174..bab0f5a 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -201,5 +201,36 @@
 	 */
 	public boolean supportsThreadFilters();
 	
+	/**
+	 * Returns a collection of identifiers of breakpoint listener extensions registered
+	 * for this breakpoint, possibly empty.
+	 * 
+	 * @return breakpoint listener extension identifiers registered on this breakpoint
+	 * @throws CoreException if unable to retrieve the collection
+	 * @since 3.5
+	 */
+	public String[] getBreakpointListeners() throws CoreException;
+	
+	/**
+	 * Adds the breakpoint listener extension with specified identifier to this breakpoint.
+	 * Has no effect if an identical listener is already registered. 
+	 *  
+	 * @param identifier breakpoint listener extension identifier
+	 * @throws CoreException if unable to add the listener
+	 * @since 3.5
+	 */
+	public void addBreakpointListener(String identifier) throws CoreException;
+	
+	/**
+	 * Removes the breakpoint listener extension with the specified identifier from this
+	 * breakpoint and returns whether the listener was removed.
+	 * 
+	 * @param identifier breakpoint listener extension identifier 
+	 * @return whether the listener was removed
+	 * @throws CoreException if an error occurs removing the listener
+	 * @since 3.5
+	 */
+	public boolean removeBreakpointListener(String identifier) throws CoreException;
+
 }
 
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpointListener.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpointListener.java
index e0218fb..65a0133 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpointListener.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaBreakpointListener.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -18,9 +18,18 @@
  * Provides event and error notification for Java breakpoints.
  * Listeners register with the <code>JDIDebugModel</code>.
  * <p>
+ * Since 3.5, clients can also register breakpoint listeners using the
+ * <code>org.eclipse.jdt.debug.breakpointListeners</code> extension point.
+ * A listener can be contributed to receive notifications from all Java
+ * breakpoints or receive notifications about specific breakpoints by
+ * programmatically registering the extension with a breakpoint.
+ * </p>
+ * <p>
  * Clients are intended to implement this interface.
  * </p>
  * @since 2.0
+ * @see JDIDebugModel
+ * @see IJavaBreakpoint
  */
 public interface IJavaBreakpointListener {
 	
@@ -108,12 +117,16 @@
 	 * Notification that the given breakpoint has been hit
 	 * in the specified thread. Allows this listener to
 	 * vote to determine if the given thread should be suspended in
-	 * reponse to the breakpoint. If at least one listener votes to
+	 * response to the breakpoint. If at least one listener votes to
 	 * <code>SUSPEND</code>, the thread will suspend. If there
 	 * are no votes to suspend the thread, there must be at least one
 	 * <code>DONT_SUSPEND</code> vote to avoid the suspension (resume). If all
 	 * listeners vote <code>DONT_CARE</code>, the thread will suspend by default.
-	 * 
+	 * <p>
+	 * The thread the breakpoint has been encountered in is now suspended. Listeners
+	 * may query thread state and perform evaluations. All subsequent breakpoints
+	 * in this thread will be ignored until voting has completed.
+	 * </p>
 	 * @param thread Java thread
 	 * @param breakpoint Java breakpoint
 	 * @return whether the thread should suspend or whether this
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaThread.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaThread.java
index 007b506..8336d94 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaThread.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaThread.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -144,7 +144,12 @@
 	 * as specified by <code>evaluationDetail</code> (one of
 	 * <code>DebugEvent.EVALUATION</code> or
 	 * <code>DebugEvent.EVALUATION_IMPLICIT</code>).
-	 * 
+	 * <p>
+	 * Since 3.5, the <code>org.eclipse.jdt.debug.breakpointListeners</code> extension
+	 * point supports evaluation execution during a listener call back. Suspend and
+	 * resume events are not fired during listener call backs. Unspecified model specific
+	 * events are fired. 
+	 * </p>
 	 * @param evaluation the evaluation to perform
 	 * @param monitor progress monitor (may be <code>null</code>
 	 * @param evaluationDetail one of <code>DebugEvent.EVALUATION</code> or
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/EventDispatcher.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/EventDispatcher.java
index 6e02c32..5829e76 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/EventDispatcher.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/EventDispatcher.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -103,17 +103,18 @@
 	 * 
 	 * @param eventSet events to dispatch
 	 */
-	protected void dispatch(EventSet eventSet) {
+	private void dispatch(EventSet eventSet) {
 		if (isShutdown()) {
 			return;
 		}
 		EventIterator iter= eventSet.eventIterator();
+		IJDIEventListener[] listeners = new IJDIEventListener[eventSet.size()];
 		boolean vote = false; 
 		boolean resume = true;
 		int voters = 0; 
-		Event winningEvent = null;
-		IJDIEventListener winner = null;
+		int index=-1;
 		while (iter.hasNext()) {
+			index++;
 			if (isShutdown()) {
 				return;
 			}
@@ -123,6 +124,7 @@
 			}
 			// Dispatch events to registered listeners, if any
 			IJDIEventListener listener = (IJDIEventListener)fEventHandlers.get(event.request());
+			listeners[index] = listener;
 			if (listener != null) {
 				if (listener instanceof IJavaLineBreakpoint) {
 					// Event dispatch to conditional breakpoints is deferred until after
@@ -137,12 +139,8 @@
 					}
 				}
 				vote = true;
-				resume = listener.handleEvent(event, fTarget) && resume;
+				resume = listener.handleEvent(event, fTarget, !resume) && resume;
 				voters++;
-				if (!resume && winner == null) {
-					winner = listener;
-					winningEvent = event;
-				}
 				continue;
 			}
 			
@@ -156,41 +154,48 @@
 			} else if (event instanceof VMStartEvent) {
 				fTarget.handleVMStart((VMStartEvent)event);
 			} else {
-				// Unhandled Event
+				// not handled
 			}
 		}
 		
-		if (resume) {
-			// process deferred events if event handlers have voted
-			// to resume the thread
-			if (!getDeferredEvents().isEmpty()) {
-				Iterator deferredIter= getDeferredEvents().iterator();
-				while (deferredIter.hasNext()) {
-					if (isShutdown()) {
-						return;
-					}
-					Event event= (Event)deferredIter.next();
-					if (event == null) {
-						continue;
-					}
-					// Dispatch events to registered listeners, if any
-					IJDIEventListener listener = (IJDIEventListener)fEventHandlers.get(event.request());
-					if (listener != null) {
-						vote = true;
-						resume = listener.handleEvent(event, fTarget) && resume;
-						continue;
-					}
+		// process deferred conditional breakpoint events
+		if (!getDeferredEvents().isEmpty()) {
+			Iterator deferredIter= getDeferredEvents().iterator();
+			while (deferredIter.hasNext()) {
+				if (isShutdown()) {
+					return;
+				}
+				Event event= (Event)deferredIter.next();
+				if (event == null) {
+					continue;
+				}
+				// Dispatch events to registered listeners, if any
+				IJDIEventListener listener = (IJDIEventListener)fEventHandlers.get(event.request());
+				if (listener != null) {
+					vote = true;
+					resume = listener.handleEvent(event, fTarget, !resume) && resume;
+					continue;
 				}
 			}
 		}
-		// clear any deferred events (processed or not)
-		getDeferredEvents().clear();
 		
-		// let the winner know they won
-		if (winner != null && voters > 1) {
-			winner.wonSuspendVote(winningEvent, fTarget);
+		// notify handlers of the end result
+		index = -1;
+		iter = eventSet.eventIterator();
+		while (iter.hasNext()) {
+			index++;
+			Event event= iter.nextEvent();
+			// notify registered listener, if any
+			IJDIEventListener listener = listeners[index];
+			if (listener != null) {
+				listener.eventSetComplete(event, fTarget, !resume);
+			}
 		}
 		
+		// clear any deferred JDI events (processed or not)
+		getDeferredEvents().clear();
+		
+		// fire queued DEBUG events
 		fireEvents();
 		
 		if (vote && resume) {
@@ -255,7 +260,7 @@
 	 * @return whether this event dispatcher has been
 	 * shutdown
 	 */
-	protected boolean isShutdown() {
+	private boolean isShutdown() {
 		return fShutdown;
 	}
 	
@@ -276,7 +281,7 @@
 	/**
 	 * Deregisters the given listener and event request. The listener
 	 * will no longer be notified of events associated with the request.
-	 * Listeners are responsible for deleting the assocaited event
+	 * Listeners are responsible for deleting the associated event
 	 * request if required.
 	 * 
 	 * @param listener the listener to deregister
@@ -299,9 +304,9 @@
 	/**
 	 * Fires debug events in the event queue, and clears the queue
 	 */
-	protected void fireEvents() {
+	private void fireEvents() {
 		DebugPlugin plugin= DebugPlugin.getDefault();
-		if (plugin != null) { //check that not in the process of shutting down
+		if (plugin != null && fDebugEvents.size() > 0) { //check that not in the process of shutting down
 			DebugEvent[] events = (DebugEvent[])fDebugEvents.toArray(new DebugEvent[fDebugEvents.size()]);
 			fDebugEvents.clear();
 			plugin.fireDebugEventSet(events);
@@ -314,16 +319,17 @@
 	 * 
 	 * @param event event to defer
 	 */
-	protected void defer(Event event) {
+	private void defer(Event event) {
 		fDeferredEvents.add(event);
 	}
+	
 
 	/**
 	 * Returns the events currently deferred.
 	 * 
 	 * @return deferred events
 	 */
-	protected List getDeferredEvents() {
+	private List getDeferredEvents() {
 		return fDeferredEvents;
 	}
 		
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/IJDIEventListener.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/IJDIEventListener.java
index 1e5254b..940d41f 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/IJDIEventListener.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/IJDIEventListener.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -33,23 +33,27 @@
 	 * resumed. If all agree, the thread is resumed by the event dispatcher.
 	 * If any event handler returns <code>false</code> the thread in which
 	 * the event originated is left in a suspended state.
-	 * 
+	 * <p>
+	 * Event listeners are provided with the current state of the suspend vote.
+	 * For example, this could allow a conditional breakpoint to not bother
+	 * running its evaluation since the vote is already to suspend (if it coincides
+	 * with a step end).
+	 * </p>
 	 * @param event the event to handle
 	 * @param target the debug target in which the event occurred
+	 * @param suspendVote whether the current vote among event listeners is to suspend
 	 * @return whether the thread in which the event occurred should be resumed
 	 */
-	public boolean handleEvent(Event event, JDIDebugTarget target);
+	public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote);
 	
 	/**
-	 * Notifies this event handler that event that a vote to resume took place,
-	 * and that this handler voted to suspend and won that vote.
-	 * <p>
-	 * This is a fix for bug 78764.
-	 * </p>
-	 * @param event the event that was handled
-	 * @param target the target in which the event occurred
-	 * @since 3.1
+	 * Notification that all event handlers for an event set have handled their
+	 * associated events and whether the event set will suspend.
+	 * 
+	 * @param event event the listener was registered for/handled
+	 * @param target target in which the event occurred
+	 * @param suspend whether the event will cause the event thread to suspend
 	 */
-	public void wonSuspendVote(Event event, JDIDebugTarget target);
+	public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend);
 }
 
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPlugin.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPlugin.java
index 64bff34..06bd3d7 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPlugin.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPlugin.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -38,8 +38,10 @@
 import org.eclipse.jdt.debug.core.IJavaType;
 import org.eclipse.jdt.debug.core.JDIDebugModel;
 import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
+import org.eclipse.jdt.internal.debug.core.breakpoints.BreakpointListenerManager;
 import org.eclipse.jdt.internal.debug.core.hcr.JavaHotCodeReplaceManager;
 import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
+import org.eclipse.jdt.internal.debug.core.model.JDIThread;
 import org.osgi.framework.BundleContext;
 
 import com.sun.jdi.VirtualMachineManager;
@@ -90,6 +92,14 @@
 	 * @since 3.1
 	 */
 	public static final String EXTENSION_POINT_JAVA_LOGICAL_STRUCTURES= "javaLogicalStructures"; //$NON-NLS-1$
+	
+	
+	/**
+	 * Extension point for java breakpoint action delegates.
+	 * 
+	 * @since 3.5
+	 */
+	public static final String EXTENSION_POINT_JAVA_BREAKPOINT_LISTENERS= "breakpointListeners"; //$NON-NLS-1$
 
 	/**
 	 * Status code indicating an unexpected error.
@@ -159,6 +169,11 @@
 	public static IStatus STATUS_GET_EVALUATION_FRAME = new Status(IStatus.INFO, getUniqueIdentifier(), INFO_EVALUATION_STACK_FRAME, "Provides thread context for an evaluation", null); //$NON-NLS-1$
 	
 	/**
+	 * Manages breakpoint listener extensions
+	 */
+	private BreakpointListenerManager fJavaBreakpointManager;
+	
+	/**
 	 * Returns whether the debug UI plug-in is in trace
 	 * mode.
 	 * 
@@ -245,6 +260,7 @@
 		});
 		JavaHotCodeReplaceManager.getDefault().startup();
 		fBreakpointListeners = new ListenerList();
+		fJavaBreakpointManager = new BreakpointListenerManager();
 	}
 	
 	/**
@@ -424,15 +440,77 @@
 	private BreakpointNotifier getBreakpointNotifier() {
 		return new BreakpointNotifier();
 	}
+	
+	abstract class AbstractNotifier implements ISafeRunnable {
+		
+		private IJavaBreakpoint fBreakpoint;
+		private IJavaBreakpointListener fListener;
+		
+		/**
+		 * Iterates through listeners calling this notifier's safe runnable.
+		 */
+		protected void notifyListeners(IJavaBreakpoint breakpoint) {
+			fBreakpoint = breakpoint;
+			String[] ids = null;
+			try {
+				ids = breakpoint.getBreakpointListeners();
+			} catch (CoreException e) {
+				JDIDebugPlugin.log(e);
+			}
+			// breakpoint specific listener extensions
+			if (ids != null && ids.length > 0) {
+				for (int i = 0; i < ids.length; i++) {
+					fListener = fJavaBreakpointManager.getBreakpointListener(ids[i]);
+					if (fListener != null) {
+						SafeRunner.run(this);
+					}
+				}
+			}
+			// global listener extensions
+			IJavaBreakpointListener[] global = fJavaBreakpointManager.getGlobalListeners();
+			if (global.length > 0) {
+				for (int i = 0; i < global.length; i++) {
+					fListener = global[i];
+					SafeRunner.run(this);
+				}
+			}
+			// programmatic global listeners
+			Object[] listeners = fBreakpointListeners.getListeners();
+			for (int i = 0; i < listeners.length; i++) {
+				fListener = (IJavaBreakpointListener)listeners[i];
+                SafeRunner.run(this);
+			}
+			fBreakpoint = null;
+			fListener = null;
+		}
+		
+		/**
+		 * Returns the breakpoint for which notification is proceeding or
+		 * <code>null</code> if not in notification.
+		 * 
+		 * @return breakpoint or <code>null</code>
+		 */
+		protected IJavaBreakpoint getBreakpoint() {
+			return fBreakpoint;
+		}
+		
+		/**
+		 * Returns the listener for which notification is proceeding or <code>null</code>
+		 * if not in notification loop.
+		 * 
+		 * @return breakpoint listener or <code>null</code>
+		 */
+		protected IJavaBreakpointListener getListener() {
+			return fListener;
+		}
+	}	
 
-	class BreakpointNotifier implements ISafeRunnable {
+	class BreakpointNotifier extends AbstractNotifier {
 		
 		private IJavaDebugTarget fTarget;
-		private IJavaBreakpoint fBreakpoint;
 		private int fKind;
 		private Message[] fErrors;
 		private DebugException fException;
-		private IJavaBreakpointListener fListener;
 		
 		/**
 		 * @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
@@ -446,19 +524,19 @@
 		public void run() throws Exception {
 			switch (fKind) {
 				case ADDING:
-					fListener.addingBreakpoint(fTarget, fBreakpoint);
+					getListener().addingBreakpoint(fTarget, getBreakpoint());
 					break;
 				case INSTALLED:
-					fListener.breakpointInstalled(fTarget, fBreakpoint);
+					getListener().breakpointInstalled(fTarget, getBreakpoint());
 					break;
 				case REMOVED:
-					fListener.breakpointRemoved(fTarget, fBreakpoint);
+					getListener().breakpointRemoved(fTarget, getBreakpoint());
 					break;		
 				case COMPILATION_ERRORS:
-					fListener.breakpointHasCompilationErrors((IJavaLineBreakpoint)fBreakpoint, fErrors);
+					getListener().breakpointHasCompilationErrors((IJavaLineBreakpoint)getBreakpoint(), fErrors);
 					break;
 				case RUNTIME_EXCEPTION:
-					fListener.breakpointHasRuntimeException((IJavaLineBreakpoint)fBreakpoint, fException);
+					getListener().breakpointHasRuntimeException((IJavaLineBreakpoint)getBreakpoint(), fException);
 					break;	
 			}			
 		}
@@ -475,20 +553,13 @@
 		 */
 		public void notify(IJavaDebugTarget target, IJavaBreakpoint breakpoint, int kind, Message[] errors, DebugException exception) {
 			fTarget = target;
-			fBreakpoint = breakpoint;
 			fKind = kind;
 			fErrors = errors;
 			fException = exception;
-			Object[] listeners = fBreakpointListeners.getListeners();
-			for (int i = 0; i < listeners.length; i++) {
-				fListener = (IJavaBreakpointListener)listeners[i];
-                SafeRunner.run(this);
-			}
+			notifyListeners(breakpoint);
 			fTarget = null;
-			fBreakpoint = null;
 			fErrors = null;
 			fException = null;
-			fListener = null;
 		}
 	}
 	
@@ -496,12 +567,10 @@
 		return new InstallingNotifier();
 	}
 		
-	class InstallingNotifier implements ISafeRunnable {
+	class InstallingNotifier extends AbstractNotifier {
 		
 		private IJavaDebugTarget fTarget;
-		private IJavaBreakpoint fBreakpoint;
 		private IJavaType fType;
-		private IJavaBreakpointListener fListener;
 		private int fInstall;
 		
 		/**
@@ -514,14 +583,12 @@
 		 * @see org.eclipse.core.runtime.ISafeRunnable#run()
 		 */
 		public void run() throws Exception {
-			fInstall = fInstall | fListener.installingBreakpoint(fTarget, fBreakpoint, fType);		
+			fInstall = fInstall | getListener().installingBreakpoint(fTarget, getBreakpoint(), fType);		
 		}
 		
 		private void dispose() {
 			fTarget = null;
-			fBreakpoint = null;
 			fType = null;
-			fListener = null;
 		}
 
 		/**
@@ -536,14 +603,9 @@
 		 */
 		public boolean notifyInstalling(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
 			fTarget = target;
-			fBreakpoint = breakpoint;
 			fType = type;
 			fInstall = IJavaBreakpointListener.DONT_CARE;
-			Object[] listeners = fBreakpointListeners.getListeners();
-			for (int i = 0; i < listeners.length; i++) {
-				fListener = (IJavaBreakpointListener)listeners[i];
-                SafeRunner.run(this);
-			}
+			notifyListeners(breakpoint);
 			dispose();
 			// install if any listener voted to install, or if no one voted to not install
 			return (fInstall & IJavaBreakpointListener.INSTALL) > 0 ||
@@ -555,11 +617,9 @@
 		return new HitNotifier();
 	}
 		
-	class HitNotifier implements ISafeRunnable {
+	class HitNotifier extends AbstractNotifier {
 		
 		private IJavaThread fThread;
-		private IJavaBreakpoint fBreakpoint;
-		private IJavaBreakpointListener fListener;
 		private int fSuspend;
 		
 		/**
@@ -572,7 +632,15 @@
 		 * @see org.eclipse.core.runtime.ISafeRunnable#run()
 		 */
 		public void run() throws Exception {
-			fSuspend = fSuspend | fListener.breakpointHit(fThread, fBreakpoint);
+			if (fThread instanceof JDIThread) {
+				if (((JDIThread)fThread).hasClientRequestedSuspend()) {
+					// abort notification to breakpoint listeners if a client suspend
+					// request is received
+					fSuspend = fSuspend | IJavaBreakpointListener.SUSPEND;
+					return;
+				}
+			}
+			fSuspend = fSuspend | getListener().breakpointHit(fThread, getBreakpoint());
 		}
 
 		/**
@@ -585,16 +653,9 @@
 		 */
 		public boolean notifyHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
 			fThread = thread;
-			fBreakpoint = breakpoint;
-			Object[] listeners = fBreakpointListeners.getListeners();
 			fSuspend = IJavaBreakpointListener.DONT_CARE;
-			for (int i = 0; i < listeners.length; i++) {
-				fListener = (IJavaBreakpointListener)listeners[i];
-                SafeRunner.run(this);
-			}
+			notifyListeners(breakpoint);
 			fThread = null;
-			fBreakpoint = null;
-			fListener = null;
 			// Suspend if any listener voted to suspend or no one voted "don't suspend"
 			return (fSuspend & IJavaBreakpointListener.SUSPEND) > 0 ||
 					(fSuspend & IJavaBreakpointListener.DONT_SUSPEND) == 0;
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java
new file mode 100644
index 0000000..77ddf89
--- /dev/null
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/BreakpointListenerManager.java
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jdt.internal.debug.core.breakpoints;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugException;
+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.IJavaThread;
+import org.eclipse.jdt.debug.core.IJavaType;
+import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
+
+/**
+ * Manages breakpoint listener extensions.
+ * 
+ * @since 3.5
+ */
+public class BreakpointListenerManager {
+
+	/**
+	 * Map java breakpoint listeners by id
+	 */
+	private static Map fgJavaBreakpointListenersMap;
+	
+	/**
+	 * Global listeners
+	 */
+	private static IJavaBreakpointListener[] fgGlobalListeners;
+	
+	private static final String VALUE_GLOBAL = "*"; //$NON-NLS-1$
+	private static final String ATTR_ID = "id"; //$NON-NLS-1$
+	private static final String ATTR_CLASS = "class"; //$NON-NLS-1$
+	private static final String ATTR_FILTER = "filter"; //$NON-NLS-1$
+	
+	/**
+	 * Proxy to a breakpoint listener
+	 */
+	private class JavaBreakpointListenerProxy implements IJavaBreakpointListener {
+
+		private IConfigurationElement fConfigElement;
+		private IJavaBreakpointListener fDelegate;
+		
+		public JavaBreakpointListenerProxy(IConfigurationElement element) {
+			fConfigElement = element;
+		}
+		
+		/**
+		 * Returns the underlying delegate or <code>null</code> if none/error
+		 * 
+		 * @return breakpoint listener extension
+		 */
+		private synchronized IJavaBreakpointListener getDelegate() {
+			if (fDelegate == null) {			
+				try {
+					fDelegate = (IJavaBreakpointListener) fConfigElement.createExecutableExtension(ATTR_CLASS);
+				} catch (CoreException e) {
+					JDIDebugPlugin.log(e);
+				}
+			}
+			
+			return fDelegate;							
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#addingBreakpoint(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+		 */
+		public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				delegate.addingBreakpoint(target, breakpoint);
+			}
+		}
+		
+		/**
+		 * Whether this listener is for all breakpoints.
+		 * 
+		 * @return whether for all breakpoints
+		 */
+		boolean isGlobal() {
+			String filter = fConfigElement.getAttribute(ATTR_FILTER);
+			if (filter != null && filter.equals(VALUE_GLOBAL)) {
+				return true;
+			}
+			return false;
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointHasCompilationErrors(org.eclipse.jdt.debug.core.IJavaLineBreakpoint, org.eclipse.jdt.core.dom.Message[])
+		 */
+		public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				delegate.breakpointHasCompilationErrors(breakpoint, errors);
+			}
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointHasRuntimeException(org.eclipse.jdt.debug.core.IJavaLineBreakpoint, org.eclipse.debug.core.DebugException)
+		 */
+		public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				delegate.breakpointHasRuntimeException(breakpoint, exception);
+			}
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointHit(org.eclipse.jdt.debug.core.IJavaThread, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+		 */
+		public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				return delegate.breakpointHit(thread, breakpoint);
+			}
+			return IJavaBreakpointListener.DONT_CARE;
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointInstalled(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+		 */
+		public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				delegate.breakpointInstalled(target, breakpoint);
+			}
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#breakpointRemoved(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint)
+		 */
+		public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				delegate.breakpointRemoved(target, breakpoint);
+			}
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jdt.debug.core.IJavaBreakpointListener#installingBreakpoint(org.eclipse.jdt.debug.core.IJavaDebugTarget, org.eclipse.jdt.debug.core.IJavaBreakpoint, org.eclipse.jdt.debug.core.IJavaType)
+		 */
+		public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
+			IJavaBreakpointListener delegate = getDelegate();
+			if (delegate != null) {
+				delegate.installingBreakpoint(target, breakpoint, type);
+			}
+			return IJavaBreakpointListener.DONT_CARE;
+		}
+		
+	}
+	
+	/**
+	 * Load extensions.
+	 */
+	private synchronized void init() {
+		if (fgJavaBreakpointListenersMap == null) {
+			fgJavaBreakpointListenersMap = new HashMap();
+			List global = new ArrayList(); 
+			IExtensionPoint extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(JDIDebugPlugin.getUniqueIdentifier(), JDIDebugPlugin.EXTENSION_POINT_JAVA_BREAKPOINT_LISTENERS);
+			IConfigurationElement[] actionDelegateElements= extensionPoint.getConfigurationElements();
+			for (int i= 0; i < actionDelegateElements.length; i++) {
+				try {
+					String id = actionDelegateElements[i].getAttribute(ATTR_ID);
+					if (id == null)
+						throw new CoreException(new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), "Java breakpoint listener requires an  identifier attribute.")); //$NON-NLS-1$
+					JavaBreakpointListenerProxy listener = new JavaBreakpointListenerProxy(actionDelegateElements[i]);
+					fgJavaBreakpointListenersMap.put(id, listener);
+					if (listener.isGlobal()) {
+						global.add(listener);
+					}
+				} catch (CoreException e) {
+					JDIDebugPlugin.log(e);
+				}
+			}
+			fgGlobalListeners = (IJavaBreakpointListener[]) global.toArray(new IJavaBreakpointListener[global.size()]);
+		}
+	}
+
+	/**
+	 * Returns the listener registered with the given identifier or <code>null</code>
+	 * if none.
+	 * 
+	 * @param id extension identifier
+	 * @return breakpoint listener or <code>null</code>
+	 */
+	public IJavaBreakpointListener getBreakpointListener(String id) {
+		init();
+		return (IJavaBreakpointListener)fgJavaBreakpointListenersMap.get(id);
+	}
+
+	/**
+	 * Returns breakpoint listener extensions registered to listen for changes to
+	 * all breakpoints.
+	 * 
+	 * @return global listeners
+	 */
+	public IJavaBreakpointListener[] getGlobalListeners() {
+		init();
+		return fgGlobalListeners;
+	}
+}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/ConditionalBreakpointHandler.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/ConditionalBreakpointHandler.java
new file mode 100644
index 0000000..206d3b7
--- /dev/null
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/ConditionalBreakpointHandler.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * 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:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.debug.core.breakpoints;
+
+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.IJavaPrimitiveValue;
+import org.eclipse.jdt.debug.core.IJavaStackFrame;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.core.IJavaType;
+import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
+import org.eclipse.jdt.debug.eval.ICompiledExpression;
+import org.eclipse.jdt.debug.eval.IEvaluationListener;
+import org.eclipse.jdt.debug.eval.IEvaluationResult;
+import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
+import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
+import org.eclipse.jdt.internal.debug.core.model.JDIThread;
+
+import com.ibm.icu.text.MessageFormat;
+import com.sun.jdi.VMDisconnectedException;
+
+/**
+ * Breakpoint listener to handle breakpoint conditions.
+ * 
+ * @since 3.5
+ */
+public class ConditionalBreakpointHandler implements IJavaBreakpointListener {
+	
+	/**
+	 * 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 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 DONT_SUSPEND;
+				} else {
+					fireConditionHasRuntimeErrors(fBreakpoint, exception);
+					return SUSPEND;
+				}
+			} else {
+				try {
+					IValue value= result.getValue();
+					if (fBreakpoint.isConditionSuspendOnTrue()) {
+						if (value instanceof IJavaPrimitiveValue) {
+							// Suspend when the condition evaluates true
+							IJavaPrimitiveValue javaValue= (IJavaPrimitiveValue)value;
+							if (javaValue.getJavaType().getName().equals("boolean")) { //$NON-NLS-1$
+								if (javaValue.getBooleanValue()) {
+									return SUSPEND;
+								} else {
+									return DONT_SUSPEND;
+								}
+							}
+						}
+						// result was not boolean
+						fireConditionHasRuntimeErrors(fBreakpoint, new DebugException(
+								new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), 
+										MessageFormat.format(JDIDebugBreakpointMessages.ConditionalBreakpointHandler_1, new String[]{value.getReferenceTypeName()}))));
+						return SUSPEND;
+					} else {
+						IDebugTarget debugTarget= thread.getDebugTarget();
+						IValue lastValue= fBreakpoint.setCurrentConditionValue(debugTarget, value);
+						if (!value.equals(lastValue)) {
+							return SUSPEND;
+						} else {
+							return DONT_SUSPEND;
+						}
+					}
+				} catch (DebugException e) {
+					// Suspend when an error occurs
+					JDIDebugPlugin.log(e);
+					return SUSPEND;
+				}
+			}
+		}
+		
+		/**
+		 * 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;
+		}
+	}	
+
+	public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {
+	}
+
+	public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
+	}
+
+	public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
+		if (breakpoint instanceof IJavaLineBreakpoint) {
+			JavaLineBreakpoint lineBreakpoint = (JavaLineBreakpoint) breakpoint;
+			try {
+				final String condition= lineBreakpoint.getCondition();
+				if (condition == null) {
+					return SUSPEND;
+				}
+				EvaluationListener listener= new EvaluationListener(lineBreakpoint);
+				IJavaStackFrame frame = (IJavaStackFrame) thread.getTopStackFrame(); 
+				IJavaProject project= lineBreakpoint.getJavaProject(frame);
+				if (project == null) {
+					fireConditionHasErrors(lineBreakpoint, new Message[]{new Message(JDIDebugBreakpointMessages.JavaLineBreakpoint_Unable_to_compile_conditional_breakpoint___missing_Java_project_context__1, -1)});
+					return SUSPEND;
+				}
+				IJavaDebugTarget target = (IJavaDebugTarget) thread.getDebugTarget();
+				IAstEvaluationEngine engine = getEvaluationEngine(target, project);
+				if (engine == null) {
+					// If no engine is available, suspend
+					return SUSPEND;
+				}
+				ICompiledExpression expression= lineBreakpoint.getExpression(thread);
+				if (expression == null) {
+					expression= engine.getCompiledExpression(condition, frame);
+					lineBreakpoint.setExpression(thread, expression);
+				}
+				if (expression.hasErrors()) {
+					fireConditionHasErrors(lineBreakpoint, getMessages(expression));
+					return SUSPEND;
+				}
+				Object lock = listener.getLock();
+				synchronized (lock) {
+					engine.evaluateExpression(expression, 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 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 SUSPEND;
+	}
+
+	public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
+	}
+
+	public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
+		return 0;
+	}
+
+	/**
+	 * Returns an evaluation engine for evaluating this breakpoint's condition
+	 * in the given target and project context.
+	 */
+	private IAstEvaluationEngine getEvaluationEngine(IJavaDebugTarget vm, IJavaProject project)   {
+		return ((JDIDebugTarget)vm).getEvaluationEngine(project);
+	}	
+	
+	private void fireConditionHasRuntimeErrors(IJavaLineBreakpoint breakpoint, DebugException 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) {
+		JDIDebugPlugin.getDefault().fireBreakpointHasCompilationErrors(breakpoint, messages);
+	}
+	
+	/**
+	 * Convert an array of <code>String</code> to an array of
+	 * <code>Message</code>.
+	 */
+	private Message[] getMessages(ICompiledExpression expression) {
+		String[] errorMessages= expression.getErrorMessages();
+		Message[] messages= new Message[errorMessages.length];
+		for (int i= 0; i < messages.length; i++) {
+			messages[i]= new Message(errorMessages[i], -1);
+		}
+		return messages;
+	}	
+}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.java
index cb27765..c235156 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -15,6 +15,10 @@
 public class JDIDebugBreakpointMessages extends NLS {
 	private static final String BUNDLE_NAME = "org.eclipse.jdt.internal.debug.core.breakpoints.JDIDebugBreakpointMessages";//$NON-NLS-1$
 
+	public static String ConditionalBreakpointHandler_0;
+
+	public static String ConditionalBreakpointHandler_1;
+
 	public static String JavaBreakpoint___Hit_Count___0___1;
 	public static String JavaBreakpoint_Exception;
 	public static String JavaPatternBreakpoint_0;
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.properties b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.properties
index d206e7b..40c8ec1 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.properties
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JDIDebugBreakpointMessages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2007 IBM Corporation and others.
+# Copyright (c) 2000, 2009 IBM Corporation and others.
 # 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
@@ -9,6 +9,8 @@
 #     IBM Corporation - initial API and implementation
 ###############################################################################
 
+ConditionalBreakpointHandler_0=Conditional breakpoint evaluation interrupted
+ConditionalBreakpointHandler_1=Result of breakpoint conditional expression was not a boolean: {0}
 JavaBreakpoint___Hit_Count___0___1=\ [hit count: {0}]
 JavaBreakpoint_Exception=Exception occurred while updating breakpoint.
 JavaPatternBreakpoint_0=Breakpoint installation failed
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java
index 0b27443..ea3a0a3 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaBreakpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -90,7 +90,14 @@
 	 * to <code>IJavaBreakpoint.SUSPEND_VM</code> or
 	 * <code>IJavaBreakpoint.SUSPEND_THREAD</code>.
 	 */
-	protected static final String SUSPEND_POLICY = "org.eclipse.jdt.debug.core.suspendPolicy"; //$NON-NLS-1$			
+	protected static final String SUSPEND_POLICY = "org.eclipse.jdt.debug.core.suspendPolicy"; //$NON-NLS-1$	
+	
+	/**
+	 * Breakpoint attribute storing a comma delimited list of extension identifiers of
+	 * breakpoint listeners. The listeners will be notified in the order specified in the list.
+	 * @since 3.5
+	 */
+	public static final String BREAKPOINT_LISTENERS = JDIDebugPlugin.EXTENSION_POINT_JAVA_BREAKPOINT_LISTENERS;
 	
 	/**
 	 * Stores the collection of requests that this breakpoint has installed in
@@ -248,22 +255,22 @@
 	/* (non-Javadoc)
 	 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#handleEvent(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
 	 */
-	public boolean handleEvent(Event event, JDIDebugTarget target) {
+	public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote) {
 		if (event instanceof ClassPrepareEvent) {
-			return handleClassPrepareEvent((ClassPrepareEvent)event, target);
+			return handleClassPrepareEvent((ClassPrepareEvent)event, target, suspendVote);
 		}
 		ThreadReference threadRef= ((LocatableEvent)event).thread();
 		JDIThread thread= target.findThread(threadRef);	
 		if (thread == null || thread.isIgnoringBreakpoints()) {
 			return true;
 		}
-		return handleBreakpointEvent(event, target, thread);		
+		return handleBreakpointEvent(event, thread, suspendVote);		
 	}
 	
 	/* (non-Javadoc)
-	 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
+	 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#eventSetComplete(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, boolean)
 	 */
-	public void wonSuspendVote(Event event, JDIDebugTarget target) {
+	public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend) {
 		ThreadReference threadRef = null;
 		if (event instanceof ClassPrepareEvent) {
 			threadRef = ((ClassPrepareEvent)event).thread();
@@ -277,7 +284,15 @@
 		if (thread == null || thread.isIgnoringBreakpoints()) {
 			return;
 		}
-		thread.wonSuspendVote(this);
+		if (event instanceof ClassPrepareEvent) {
+			classPrepareComplete(event, thread, suspend);
+		} else {
+			thread.completeBreakpointHandling(this, suspend, true);
+		}
+	}
+	
+	protected void classPrepareComplete(Event event, JDIThread thread, boolean suspend) {
+		// do nothing 
 	}
 
 	/**
@@ -287,7 +302,7 @@
 	 * If the class which has been loaded is a class in which this breakpoint
 	 * should install, create a breakpoint request for that class.
 	 */	
-	public boolean handleClassPrepareEvent(ClassPrepareEvent event, JDIDebugTarget target) {
+	public boolean handleClassPrepareEvent(ClassPrepareEvent event, JDIDebugTarget target, boolean suspendVote) {
 		try {
 			if (!installableReferenceType(event.referenceType(), target)) {
 				// Don't install this breakpoint in an
@@ -312,9 +327,9 @@
 	 * Handle the given event, which was generated by the breakpoint request
 	 * installed in the given target by this breakpoint.
 	 */
-	public boolean handleBreakpointEvent(Event event, JDIDebugTarget target, JDIThread thread) {
+	public boolean handleBreakpointEvent(Event event, JDIThread thread, boolean suspendVote) {
 		expireHitCount(event);
-		return !suspend(thread); // Resume if suspend fails
+		return !suspend(thread, suspendVote); // Resume if suspend fails
 	}
 	
 	/**
@@ -325,8 +340,8 @@
 	 * 
 	 * @see IJavaBreakpointListener#breakpointHit(IJavaThread, IJavaBreakpoint)
 	 */
-	protected boolean suspend(JDIThread thread) {
-		return thread.handleSuspendForBreakpoint(this, true);
+	protected boolean suspend(JDIThread thread, boolean suspendVote) {
+		return thread.handleSuspendForBreakpoint(this, suspendVote);
 	}
 	
 	/**
@@ -1183,4 +1198,63 @@
 	public boolean supportsThreadFilters() {
 		return true;
 	}
+	
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#addBreakpointListener(java.lang.String)
+	 */
+	public void addBreakpointListener(String identifier) throws CoreException {
+		String value = ensureMarker().getAttribute(BREAKPOINT_LISTENERS, (String) null);
+		if (value == null) {
+			value = identifier;
+		} else {
+			StringBuffer buf = new StringBuffer(value);
+			buf.append(","); //$NON-NLS-1$
+			buf.append(identifier);
+			value = buf.toString();
+		}
+		setAttribute(BREAKPOINT_LISTENERS, value);
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#removeBreakpointListener(java.lang.String)
+	 */
+	public boolean removeBreakpointListener(String identifier) throws CoreException {
+		String value = ensureMarker().getAttribute(BREAKPOINT_LISTENERS, (String) null);
+		if (value != null) {
+			String[] ids = value.split(","); //$NON-NLS-1$
+			List list = new ArrayList(ids.length);
+			for (int i = 0; i < ids.length; i++) {
+				list.add(ids[i]);
+			}
+			if (list.remove(identifier)) {
+				if (list.isEmpty()) {
+					setAttribute(BREAKPOINT_LISTENERS, null);
+				} else {
+					StringBuffer buf = new StringBuffer();
+					Iterator iterator = list.iterator();
+					while (iterator.hasNext()) {
+						String id = (String) iterator.next();
+						buf.append(id);
+						if (iterator.hasNext()) {
+							buf.append(","); //$NON-NLS-1$
+						}
+					}
+					setAttribute(BREAKPOINT_LISTENERS, buf.toString());
+				}
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getBreakpointListeners()
+	 */
+	public String[] getBreakpointListeners() throws CoreException {
+		String value = ensureMarker().getAttribute(BREAKPOINT_LISTENERS, (String) null);
+		if (value == null) {
+			return new String[0];
+		}
+		return value.split(","); //$NON-NLS-1$
+	}
 }
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaClassPrepareBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaClassPrepareBreakpoint.java
index 8f23ba6..9c19ba1 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaClassPrepareBreakpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaClassPrepareBreakpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -32,6 +32,7 @@
 import com.sun.jdi.ReferenceType;
 import com.sun.jdi.ThreadReference;
 import com.sun.jdi.event.ClassPrepareEvent;
+import com.sun.jdi.event.Event;
 import com.sun.jdi.request.ClassPrepareRequest;
 import com.sun.jdi.request.EventRequest;
 
@@ -161,7 +162,7 @@
 	/* (non-Javadoc)
 	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#handleClassPrepareEvent(com.sun.jdi.event.ClassPrepareEvent, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
 	 */
-	public boolean handleClassPrepareEvent(ClassPrepareEvent event, JDIDebugTarget target) {
+	public boolean handleClassPrepareEvent(ClassPrepareEvent event, JDIDebugTarget target, boolean suspendVote) {
 		try {
 			if (isEnabled() && event.referenceType().name().equals(getTypeName())) {
 				ThreadReference threadRef= event.thread();
@@ -169,12 +170,20 @@
 				if (thread == null || thread.isIgnoringBreakpoints()) {
 					return true;
 				}
-				return handleBreakpointEvent(event, target, thread);
+				return handleBreakpointEvent(event, thread, suspendVote);
 			}
 		} catch (CoreException e) {
 		}
 		return true;
 	}
+	
+	/* (non-Javadoc)
+	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#classPrepareComplete(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIThread, boolean)
+	 */
+	protected void classPrepareComplete(Event event, JDIThread thread, boolean suspend) {
+		thread.completeBreakpointHandling(this, suspend, true);
+	}
+	
 	/* (non-Javadoc)
 	 * @see org.eclipse.jdt.debug.core.IJavaClassPrepareBreakpoint#getMemberType()
 	 */
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaExceptionBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaExceptionBreakpoint.java
index 1881728..c5e0165 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaExceptionBreakpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaExceptionBreakpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -297,10 +297,10 @@
 	 * 
 	 * @return true if we do not want to suspend false otherwise
 	 */
-	public boolean handleBreakpointEvent(Event event, JDIDebugTarget target, JDIThread thread) {
+	public boolean handleBreakpointEvent(Event event, JDIThread thread, boolean suspendVote) {
 		if (event instanceof ExceptionEvent) {
 			ObjectReference ex = ((ExceptionEvent)event).exception(); 
-			fLastTarget = target;
+			fLastTarget = thread.getJavaDebugTarget();
 			fLastException = ex;
 			String name = null;
 			try {
@@ -316,7 +316,7 @@
 				JDIDebugPlugin.log(e);
 			} catch (RuntimeException e) {
 				try {
-					target.targetRequestFailed(e.getMessage(), e);
+					thread.targetRequestFailed(e.getMessage(), e);
 				} catch (DebugException de) {
 					JDIDebugPlugin.log(e);
 					return false;
@@ -341,11 +341,11 @@
 						excluded = matchesFilters(filters, typeName, defaultPackage);
 					}
 					if (included && !excluded) {
-						return !suspend(thread);
+						return !suspend(thread, suspendVote);
 					}
 					return true;
 				} 
-			return !suspend(thread);
+			return !suspend(thread, suspendVote);
 		}	
 		return true;
 	}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaLineBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaLineBreakpoint.java
index fe77bfb..f348ffb 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaLineBreakpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaLineBreakpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -26,7 +26,6 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 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.DebugPlugin;
 import org.eclipse.debug.core.ILaunch;
@@ -39,17 +38,12 @@
 import org.eclipse.jdt.core.IJavaElement;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jdt.core.dom.Message;
-import org.eclipse.jdt.debug.core.IJavaDebugTarget;
 import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
-import org.eclipse.jdt.debug.core.IJavaPrimitiveValue;
 import org.eclipse.jdt.debug.core.IJavaReferenceType;
+import org.eclipse.jdt.debug.core.IJavaStackFrame;
+import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.core.IJavaType;
-import org.eclipse.jdt.debug.core.JDIDebugModel;
-import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
 import org.eclipse.jdt.debug.eval.ICompiledExpression;
-import org.eclipse.jdt.debug.eval.IEvaluationListener;
-import org.eclipse.jdt.debug.eval.IEvaluationResult;
 import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
 import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
 import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame;
@@ -78,7 +72,7 @@
 	 */
 	protected static final String CONDITION= "org.eclipse.jdt.debug.core.condition"; //$NON-NLS-1$
 	/**
-	 * Breakpoint attribute storing a breakpoint's condition enablement
+	 * Breakpoint attribute storing a breakpoint's condition enabled state
 	 * (value <code>"org.eclipse.jdt.debug.core.conditionEnabled"</code>). This attribute is stored as an
 	 * <code>boolean</code>.
 	 */
@@ -379,27 +373,9 @@
 	}
 	
 	/**
-	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#handleBreakpointEvent(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, org.eclipse.jdt.internal.debug.core.model.JDIThread)
-	 * 
-	 * (From referenced JavaDoc:
-	 * 	Returns whether the thread should be resumed
-	 */
-	public boolean handleBreakpointEvent(Event event, JDIDebugTarget target, JDIThread thread) {
-		if (hasCondition()) {
-			try {
-				return handleConditionalBreakpointEvent(event, thread, target);
-			} catch (CoreException exception) {
-				JDIDebugPlugin.log(exception);
-				return !suspendForEvent(event, thread);
-			}
-		}
-		return !suspendForEvent(event, thread); // Resume if suspend fails					
-	}
-	
-	/**
 	 * Returns whether this breakpoint has an enabled condition
 	 */
-	protected boolean hasCondition() {
+	public boolean hasCondition() {
 		try {
 			String condition = getCondition();
 			return isConditionEnabled() && condition != null && (condition.length() > 0);
@@ -413,89 +389,12 @@
 	 * Suspends the given thread for the given breakpoint event. Returns
 	 * whether the thread suspends.
 	 */
-	protected boolean suspendForEvent(Event event, JDIThread thread) {
+	protected boolean suspendForEvent(Event event, JDIThread thread, boolean suspendVote) {
 		expireHitCount(event);
-		return suspend(thread);
+		return suspend(thread, suspendVote);
 	}
-	
-	/**
-	 * Suspends the given thread for the given breakpoint event after
-	 * a conditional expression evaluation. This method tells the thread
-	 * to fire a suspend event immediately instead of queue'ing the event.
-	 * This is required because of the asynchronous nature of expression
-	 * evaluation. The EventDispatcher has already fired queued events
-	 * by the time the evaluation completes.
-	 */
-	protected boolean suspendForCondition(Event event, JDIThread thread) {
-		expireHitCount(event);
-		return thread.handleSuspendForBreakpoint(this, false);
-	}
-	
-	/**
-	 * Returns whether this breakpoint should resume based on the
-	 * value of its condition.
-	 * 
-	 * If there is not an enabled condition which evaluates to <code>true</code>,
-	 * the thread should resume.
-	 */
-	protected boolean handleConditionalBreakpointEvent(Event event, JDIThread thread, JDIDebugTarget target) throws CoreException {
-		synchronized (thread) {
-			if (thread.isPerformingEvaluation()) {
-				// If an evaluation is already being computed for this thread,
-				// we can't perform another
-				return !suspendForEvent(event, thread);
-			}
-			final String condition= getCondition();
-			if (!hasCondition()) {
-				return !suspendForEvent(event, thread);
-			}
-			EvaluationListener listener= new EvaluationListener();
-	
-			int suspendPolicy= SUSPEND_THREAD;
-			try {
-				suspendPolicy= getSuspendPolicy();
-			} catch (CoreException e) {
-			}
-			if (suspendPolicy == SUSPEND_VM) {
-				((JDIDebugTarget)thread.getDebugTarget()).prepareToSuspendByBreakpoint(this);
-			} else {
-				thread.handleSuspendForBreakpointQuiet(this);
-			}
-			List frames = thread.computeNewStackFrames();
-			if (frames.size() == 0) {
-				return !suspendForEvent(event, thread);
-			}
-			JDIStackFrame frame= (JDIStackFrame)frames.get(0);
-			IJavaProject project= getJavaProject(frame);
-			if (project == null) {
-				throw new CoreException(new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED,
-					JDIDebugBreakpointMessages.JavaLineBreakpoint_Unable_to_compile_conditional_breakpoint___missing_Java_project_context__1, null)); 
-			}
-			IAstEvaluationEngine engine = getEvaluationEngine(target, project);
-			if (engine == null) {
-				// If no engine is available, suspend
-				return !suspendForEvent(event, thread);
-			}
-			ICompiledExpression expression= (ICompiledExpression)fCompiledExpressions.get(thread);
-			if (expression == null) {
-				expression= engine.getCompiledExpression(condition, frame);
-				fCompiledExpressions.put(thread, expression);
-			}
-			if (conditionHasErrors(expression)) {
-				fireConditionHasErrors(expression);
-				return !suspendForEvent(event, thread);
-			}
-			fSuspendEvents.put(thread, event);
-            thread.setEvaluatingConditionalBreakpoint(true);
-			engine.evaluateExpression(expression, frame, listener, DebugEvent.EVALUATION_IMPLICIT, false);
-	
-			// Do not resume. When the evaluation returns, the evaluation listener
-			// will resume the thread if necessary or update for suspension.
-			return false;
-		}
-	}
-	
-	private IJavaProject getJavaProject(JDIStackFrame stackFrame) {
+		
+	protected IJavaProject getJavaProject(IJavaStackFrame stackFrame) {
 	    IJavaProject project= (IJavaProject) fProjectsByFrame.get(stackFrame);
 	    if (project == null) {
 	        project = computeJavaProject(stackFrame);
@@ -506,7 +405,7 @@
 	    return project;
 	}
 	
-	private IJavaProject computeJavaProject(JDIStackFrame stackFrame) {
+	private IJavaProject computeJavaProject(IJavaStackFrame stackFrame) {
 		ILaunch launch = stackFrame.getLaunch();
 		if (launch == null) {
 			return null;
@@ -549,115 +448,6 @@
 		return null;
 	}
 	
-	/**
-	 * 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 {
-		public void evaluationComplete(IEvaluationResult result) {
-			JDIThread thread= (JDIThread)result.getThread();
-            thread.setEvaluatingConditionalBreakpoint(false);
-			Event event= (Event)fSuspendEvents.get(thread);
-			if (result.hasErrors()) {
-				DebugException exception= result.getException();
-				Throwable wrappedException= exception.getStatus().getException();
-				if (wrappedException instanceof VMDisconnectedException) {
-					JDIDebugPlugin.log(wrappedException);
-					try {
-						thread.resumeQuiet();
-					} catch(DebugException e) {
-						JDIDebugPlugin.log(e);
-					}
-				} else {
-					fireConditionHasRuntimeErrors(exception);
-					suspendForCondition(event, thread);
-					return;
-				}
-			}
-			try {
-				IValue value= result.getValue();
-				if (isConditionSuspendOnTrue()) {
-					if (value instanceof IJavaPrimitiveValue) {
-						// Suspend when the condition evaluates true
-						IJavaPrimitiveValue javaValue= (IJavaPrimitiveValue)value;
-						if (isConditionSuspendOnTrue()) {
-							if (javaValue.getJavaType().getName().equals("boolean") && javaValue.getBooleanValue()) { //$NON-NLS-1$
-								suspendForCondition(event, thread);
-								return;
-							}
-						}
-					}
-				} else {
-					IDebugTarget debugTarget= thread.getDebugTarget();
-					IValue lastValue= (IValue)fConditionValues.get(debugTarget);
-					fConditionValues.put(debugTarget, value);
-					if (!value.equals(lastValue)) {
-						suspendForCondition(event, thread);
-						return;
-					}
-				}
-				int suspendPolicy= SUSPEND_THREAD;
-				try {
-					suspendPolicy= getSuspendPolicy();
-				} catch (CoreException e) {
-				}
-				if (suspendPolicy == SUSPEND_VM) {
-					((JDIDebugTarget)thread.getDebugTarget()).resumeQuiet();
-				} else {
-					thread.resumeQuiet();
-				}
-				return;
-			} catch (DebugException e) {
-				JDIDebugPlugin.log(e);
-			}
-			// Suspend when an error occurs
-			suspendForEvent(event, thread);
-		}
-	}
-	
-	private void fireConditionHasRuntimeErrors(DebugException exception) {
-		JDIDebugPlugin.getDefault().fireBreakpointHasRuntimeException(this, exception);
-	}
-	
-	/**
-	 * Notifies listeners that a conditional breakpoint expression has been
-	 * compiled that contains errors
-	 */
-	private void fireConditionHasErrors(ICompiledExpression expression) {
-		JDIDebugPlugin.getDefault().fireBreakpointHasCompilationErrors(this, getMessages(expression));
-	}
-	
-	/**
-	 * Convert an array of <code>String</code> to an array of
-	 * <code>Message</code>.
-	 */
-	private Message[] getMessages(ICompiledExpression expression) {
-		String[] errorMessages= expression.getErrorMessages();
-		Message[] messages= new Message[errorMessages.length];
-		for (int i= 0; i < messages.length; i++) {
-			messages[i]= new Message(errorMessages[i], -1);
-		}
-		return messages;
-	}
-
-	/**
-	 * Returns whether the cached conditional expression has errors or
-	 * <code>false</code> if there is no cached expression
-	 */
-	public boolean conditionHasErrors(ICompiledExpression expression) {
-		return expression.hasErrors();
-	}
-	
-	/**
-	 * Returns an evaluation engine for evaluating this breakpoint's condition
-	 * in the given target and project context.
-	 */
-	public IAstEvaluationEngine getEvaluationEngine(IJavaDebugTarget vm, IJavaProject project)   {
-		return ((JDIDebugTarget)vm).getEvaluationEngine(project);
-	}
-	
 	/* (non-Javadoc)
 	 * @see org.eclipse.jdt.debug.core.IJavaLineBreakpoint#supportsCondition()
 	 */
@@ -749,5 +539,39 @@
 			recreate();
 		}
 	}
+	
+	/**
+	 * Returns existing compiled expression for the given thread or <code>null</code>.
+	 * 
+	 * @param thread thread the breakpoint was hit in
+	 * @return compiled expression or <code>null</code>
+	 */
+	protected ICompiledExpression getExpression(IJavaThread thread) {
+		return (ICompiledExpression) fCompiledExpressions.get(thread);
+	}
+	
+	/**
+	 * Sets the compiled expression for a thread.
+	 * 
+	 * @param thread thread the breakpoint was hit in
+	 * @param expression associated compiled expression
+	 */
+	protected void setExpression(IJavaThread thread, ICompiledExpression expression) {
+		fCompiledExpressions.put(thread, expression);
+	}
+	
+	/**
+	 * Sets the current result value of the conditional expression evaluation for this breakpoint
+	 * in the given target, and returns the previous value or <code>null</code> if none
+	 *  
+	 * @param target debug target
+	 * @param value current expression value
+	 * @return previous value or <code>null</code>
+	 */
+	protected IValue setCurrentConditionValue(IDebugTarget target, IValue value) {
+		IValue prev = (IValue) fConditionValues.get(target);
+		fConditionValues.put(target, value);
+		return prev;
+	}
 
 }
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java
index ef0990e..b0ea082 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -371,18 +371,18 @@
 	/**
 	 * @see JavaBreakpoint#handleBreakpointEvent(Event, JDIDebugTarget, JDIThread)
 	 */
-	public boolean handleBreakpointEvent(Event event, JDIDebugTarget target, JDIThread thread) {
+	public boolean handleBreakpointEvent(Event event, JDIThread thread, boolean suspendVote) {
 		if (event instanceof MethodEntryEvent) {
 			MethodEntryEvent entryEvent= (MethodEntryEvent) event;
-			fLastEventTypes.put(target, ENTRY_EVENT);
-			return handleMethodEvent(entryEvent, entryEvent.method(), target, thread);
+			fLastEventTypes.put(thread.getDebugTarget(), ENTRY_EVENT);
+			return handleMethodEvent(entryEvent, entryEvent.method(), thread, suspendVote);
 		} else if (event instanceof MethodExitEvent) {
 			MethodExitEvent exitEvent= (MethodExitEvent) event;
-			fLastEventTypes.put(target, EXIT_EVENT);
-			return handleMethodEvent(exitEvent, exitEvent.method(), target, thread);
+			fLastEventTypes.put(thread.getDebugTarget(), EXIT_EVENT);
+			return handleMethodEvent(exitEvent, exitEvent.method(), thread, suspendVote);
 		} else if (event instanceof BreakpointEvent) {
-			fLastEventTypes.put(target, ENTRY_EVENT);
-			return super.handleBreakpointEvent(event, target, thread);
+			fLastEventTypes.put(thread.getDebugTarget(), ENTRY_EVENT);
+			return super.handleBreakpointEvent(event, thread, suspendVote);
 		}
 		return true;
 	}
@@ -394,7 +394,7 @@
 	 * the event has been fired by a method invocation that this breakpoint
 	 * is interested in. If it is not, do nothing.
 	 */
-	protected boolean handleMethodEvent(LocatableEvent event, Method method, JDIDebugTarget target, JDIThread thread) {
+	protected boolean handleMethodEvent(LocatableEvent event, Method method, JDIThread thread, boolean suspendVote) {
 		try {
 			if (isNativeOnly()) {
 				if (!method.isNative()) {
@@ -430,15 +430,7 @@
 				return true;
 			}
 			// no hit count
-			if (hasCondition()) {
-				try {
-					return handleConditionalBreakpointEvent(event, thread, target);
-				} catch (CoreException exception) {
-					// log error
-					return !suspendForEvent(event, thread);
-				}
-			}
-			return !suspendForEvent(event, thread); // Resume if suspend fails					
+			return !suspendForEvent(event, thread, suspendVote); // Resume if suspend fails					
 		} catch (CoreException e) {
 			JDIDebugPlugin.log(e);
 		}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaWatchpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaWatchpoint.java
index f3e9672..350cbb9 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaWatchpoint.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaWatchpoint.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -417,13 +417,13 @@
 	 * 
 	 * Also, @see JavaBreakpoint#handleEvent(Event, JDIDebugTarget)
 	 */
-	public boolean handleEvent(Event event, JDIDebugTarget target)  {
+	public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote)  {
 		if (event instanceof AccessWatchpointEvent) {
 			fLastEventTypes.put(target, ACCESS_EVENT);
 		} else if (event instanceof ModificationWatchpointEvent) {
 			fLastEventTypes.put(target, MODIFICATION_EVENT);
 		}
-		return super.handleEvent(event, target);
+		return super.handleEvent(event, target, suspendVote);
 	}	
 	
 	
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugElement.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugElement.java
index d2f3810..d9450be 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugElement.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugElement.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -252,7 +252,7 @@
 	 * 
 	 * @return Java debug target
 	 */
-	protected JDIDebugTarget getJavaDebugTarget() {
+	public JDIDebugTarget getJavaDebugTarget() {
 		return (JDIDebugTarget)getDebugTarget();
 	}
 
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java
index 1958032..76b049e 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -170,6 +170,8 @@
 	public static String JDIThread_46;
 	public static String JDIThread_0;
 
+	public static String JDIThread_1;
+
 	public static String JDIType_exception_while_retrieving_signature;
 	public static String JDIType_exception_while_retrieving_type_name;
 
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties
index 857979b..49a5cd6 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2008 IBM Corporation and others.
+# Copyright (c) 2000, 2009 IBM Corporation and others.
 # 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
@@ -135,6 +135,7 @@
 JDIThread_47=Unable to determine thread daemon status
 JDIThread_48=Force return failed.
 JDIThread_0=Exception processing async thread queue
+JDIThread_1=Suspend failed waiting for an expression evaluation to complete.
 JDIThreadGroup_0=Error retrieving threads in thread group
 JDIThreadGroup_1=Error retrieving parent of thread group
 JDIThreadGroup_2=Error retrieving groups in thread group
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
index 85aa74e..c17c7f2 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
@@ -554,7 +554,7 @@
 			// only allow suspend if no threads are currently suspended
 			IThread[] threads= getThreads();
 			for (int i= 0, numThreads= threads.length; i < numThreads; i++) {
-				if (((JDIThread)threads[i]).isSuspended()) {
+				if (!((JDIThread)threads[i]).canSuspend()) {
 					return false;
 				}
 			}
@@ -1244,6 +1244,7 @@
 		}
 		try {
 			VirtualMachine vm = getVM();
+			prepareThreadsForClientSuspend();
 			if (vm != null) {
 				vm.suspend();
 			}
@@ -1252,6 +1253,7 @@
 			fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
 		} catch (RuntimeException e) {
 			setSuspended(false);
+			resumeThreads();
 			fireResumeEvent(DebugEvent.CLIENT_REQUEST);
 			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_suspend, new String[] {e.toString()}), e); 
 		}
@@ -1259,6 +1261,19 @@
 	}
 	
 	/**
+	 * Prepares threads to suspend (terminates evaluations, waits for invocations, etc.).
+	 * 
+	 * @exception DebugException if a thread times out
+	 */
+	protected void prepareThreadsForClientSuspend() throws DebugException {
+		Iterator threads = getThreadIterator();
+		while (threads.hasNext()) {
+			((JDIThread)threads.next()).prepareForClientSuspend();
+		}
+	}
+	
+	
+	/**
 	 * Notifies threads that they have been suspended
 	 */
 	protected void suspendThreads() {
@@ -1812,7 +1827,7 @@
 		 * @param target the target in which the thread started
 		 * @return <code>true</code> - the thread should be resumed
 		 */
-		public boolean handleEvent(Event event, JDIDebugTarget target) {
+		public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote) {
 			ThreadReference thread= ((ThreadStartEvent)event).thread();
 			try {
 				if (thread.isCollected()) {
@@ -1837,11 +1852,11 @@
 			}
 			return !jdiThread.isSuspended();
 		}
-
+		
 		/* (non-Javadoc)
-		 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
+		 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#eventSetComplete(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, boolean)
 		 */
-		public void wonSuspendVote(Event event, JDIDebugTarget target) {
+		public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend) {
 			// do nothing
 		}
 		
@@ -1903,7 +1918,7 @@
 		 * @param target the target in which the thread died
 		 * @return <code>true</code> - the thread should be resumed
 		 */
-		public boolean handleEvent(Event event, JDIDebugTarget target) {
+		public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote) {
 			ThreadReference ref= ((ThreadDeathEvent)event).thread();
 			JDIThread thread= findThread(ref);
 			if (thread != null) {
@@ -1916,9 +1931,9 @@
 		}
 		
 		/* (non-Javadoc)
-		 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
+		 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#eventSetComplete(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, boolean)
 		 */
-		public void wonSuspendVote(Event event, JDIDebugTarget target) {
+		public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend) {
 			// do nothing
 		}
 	
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIThread.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIThread.java
index 1c8e5f7..47eee6f 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIThread.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIThread.java
@@ -35,6 +35,7 @@
 import org.eclipse.jdt.core.Signature;
 import org.eclipse.jdt.debug.core.IEvaluationRunnable;
 import org.eclipse.jdt.debug.core.IJavaBreakpoint;
+import org.eclipse.jdt.debug.core.IJavaBreakpointListener;
 import org.eclipse.jdt.debug.core.IJavaObject;
 import org.eclipse.jdt.debug.core.IJavaStackFrame;
 import org.eclipse.jdt.debug.core.IJavaThread;
@@ -44,7 +45,9 @@
 import org.eclipse.jdt.debug.core.JDIDebugModel;
 import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
 import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
+import org.eclipse.jdt.internal.debug.core.breakpoints.ConditionalBreakpointHandler;
 import org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint;
+import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint;
 
 import com.ibm.icu.text.MessageFormat;
 import com.sun.jdi.BooleanValue;
@@ -87,6 +90,17 @@
 	 * Constant for the name of the main thread group.
 	 */
 	private static final String MAIN_THREAD_GROUP = "main"; //$NON-NLS-1$
+	
+	/**
+	 * @since 3.5
+	 */
+	public static final int RESUME_QUIET = 500;
+	
+	/**
+	 * @since 3.5
+	 */
+	public static final int SUSPEND_QUIET = 501;
+	
 	/**
 	 * Status code indicating that a request to suspend this thread has timed out
 	 */
@@ -130,10 +144,7 @@
 	 * Whether terminated.
 	 */
 	private boolean fTerminated;
-	/**
-	 * whether suspended but without firing the equivalent events.
-	 */
-	private boolean fSuspendedQuiet;
+
 	/**
 	 * Whether this thread is a system thread.
 	 */
@@ -152,30 +163,55 @@
 	 */
 	private List fCurrentBreakpoints = new ArrayList(2);
 	/**
-	 * Whether this thread is currently performing
-	 * an evaluation. An evaluation may involve a series
-	 * of method invocations.
+	 * Non-null when this thread is executing an evaluation runnable.
+	 * An evaluation may involve a series of method invocations.
 	 */
-	private boolean fIsPerformingEvaluation= false;
-	private IEvaluationRunnable fEvaluationRunnable;
+	private IEvaluationRunnable fEvaluationRunnable = null;
 	
 	/**
 	 * Whether this thread was manually suspended during an
 	 * evaluation.
 	 */
 	private boolean fEvaluationInterrupted = false;
+		
+	/**
+	 * <code>true</code> when there has been a request to suspend
+	 * this thread via {@link #suspend()}. Remains <code>true</code> until
+	 * there is a request to resume this thread via {@link #resume()}.
+	 */
+	private boolean fClientSuspendRequest = false;
 	
 	/**
 	 * Whether this thread is currently invoking a method.
 	 * Nested method invocations cannot be performed.
 	 */
 	private boolean fIsInvokingMethod = false;
+	
+	/**
+	 * Lock used to wait for method invocations to complete.
+	 */
+	private Object fInvocationLock = new Object();
+	
+	/**
+	 * Lock used to wait for evaluations to complete.
+	 */
+	private Object fEvaluationLock = new Object();
+	
 	/**
 	 * Whether or not this thread is currently honoring
 	 * breakpoints. This flag allows breakpoints to be
 	 * disabled during evaluations.
 	 */
 	private boolean fHonorBreakpoints= true;
+	
+	/**
+	 * Whether a suspend vote is currently in progress. While voting
+	 * this thread does not allow other breakpoints to be hit.
+	 * 
+	 * @since 3.5
+	 */
+	private boolean fSuspendVoteInProgress = false;
+	
 	/**
 	 * The kind of step that was originally requested.  Zero or
 	 * more 'secondary steps' may be performed programmatically after
@@ -200,10 +236,6 @@
 	 * Whether or not this thread is currently suspending (user-requested).
 	 */
 	private boolean fIsSuspending= false;
-    /**
-     * Whether or not this thread is currently evaluating an expression for a conditional breakpoint
-     */
-    private boolean fIsEvaluatingConditionalBreakpoint= false;
 
 	private ThreadJob fAsyncJob;
 	
@@ -338,14 +370,14 @@
 	 * @see ISuspendResume#canResume()
 	 */
 	public boolean canResume() {
-		return isSuspended() && !isSuspendedQuiet() && (!isPerformingEvaluation() || isInvokingMethod());
+		return isSuspended() && (!isPerformingEvaluation() || isInvokingMethod()) && !isSuspendVoteInProgress();
 	}
 
 	/**
 	 * @see ISuspendResume#canSuspend()
 	 */
 	public boolean canSuspend() {
-		return !isSuspended() || isSuspendedQuiet() || (isPerformingEvaluation() && !isInvokingMethod());
+		return !isSuspended() || (isPerformingEvaluation() && !isInvokingMethod()) || isSuspendVoteInProgress();
 	}
 
 	/**
@@ -386,7 +418,6 @@
 	protected boolean canStep() {
 		try {
 			return isSuspended()
-				&& !isSuspendedQuiet()
 				&& (!isPerformingEvaluation() || isInvokingMethod())
 				&& !isStepping()
 				&& getTopStackFrame() != null
@@ -465,9 +496,6 @@
 	 * @see IThread#getStackFrames()
 	 */
 	public synchronized IStackFrame[] getStackFrames() throws DebugException {
-		if (isSuspendedQuiet()) {
-			return new IStackFrame[0];
-		}
 		List list = computeStackFrames();
 		return (IStackFrame[])list.toArray(new IStackFrame[list.size()]);
 	}
@@ -622,10 +650,17 @@
 			requestFailed(JDIDebugModelMessages.JDIThread_Evaluation_failed___thread_not_suspended, null, IJavaThread.ERR_THREAD_NOT_SUSPENDED); 
 		}
 		
-		fIsPerformingEvaluation = true;
-		fEvaluationRunnable= evaluation;
-		fHonorBreakpoints= hitBreakpoints;
-		fireResumeEvent(evaluationDetail);
+		synchronized (fEvaluationLock) {
+			fEvaluationRunnable= evaluation;
+			fHonorBreakpoints= hitBreakpoints;
+		}
+		boolean quiet = isSuspendVoteInProgress();
+		if (quiet) {
+			// evaluations are quiet when a suspend vote is in progress (conditional breakpoints, etc.).
+			fireEvent(new DebugEvent(this, DebugEvent.MODEL_SPECIFIC, RESUME_QUIET));
+		} else {
+			fireResumeEvent(evaluationDetail);
+		}
 		//save and restore current breakpoint information - bug 30837
 		IBreakpoint[] breakpoints = getBreakpoints();
 		ISchedulingRule rule = null;
@@ -645,15 +680,21 @@
 			if (rule != null) {
 				Job.getJobManager().endRule(rule);
 			}
-			fIsPerformingEvaluation = false;
-			fEvaluationRunnable= null;
-			fHonorBreakpoints= true;
+			synchronized (fEvaluationLock) {
+				fEvaluationRunnable= null;
+				fHonorBreakpoints= true;
+				fEvaluationLock.notifyAll();
+			}
 			if (getBreakpoints().length == 0 && breakpoints.length > 0) {
 				for (int i = 0; i < breakpoints.length; i++) {
 					addCurrentBreakpoint(breakpoints[i]);
 				} 
 			}
-			fireSuspendEvent(evaluationDetail);
+			if (quiet) {
+				fireEvent(new DebugEvent(this, DebugEvent.MODEL_SPECIFIC, SUSPEND_QUIET));
+			} else {
+				fireSuspendEvent(evaluationDetail);
+			}
 			if (fEvaluationInterrupted && (fAsyncJob == null || fAsyncJob.isEmpty()) && (fRunningAsyncJob == null || fRunningAsyncJob.isEmpty())) {
 				// @see bug 31585:
 				// When an evaluation was interrupted & resumed, the launch view does
@@ -674,13 +715,13 @@
 	 * run an evaluation
 	 */
 	protected boolean canRunEvaluation() {
-		// NOTE similar to #canStep, except a quiet suspend state is OK
+		// NOTE similar to #canStep, except an evaluation can be run when in the middle of
+		// a step (conditional breakpoint, breakpoint listener, etc.)
 		try {
-			return isSuspendedQuiet() || (isSuspended()
+			return isSuspended()
 				&& !(isPerformingEvaluation() || isInvokingMethod())
-				&& !isStepping()
 				&& getTopStackFrame() != null
-				&& !getJavaDebugTarget().isPerformingHotCodeReplace());
+				&& !getJavaDebugTarget().isPerformingHotCodeReplace();
 		} catch (DebugException e) {
 			return false;
 		}
@@ -700,8 +741,11 @@
 	 * @see IJavaThread#terminateEvaluation()
 	 */
 	public void terminateEvaluation() throws DebugException {
-		if (canTerminateEvaluation()) {
-			((ITerminate) fEvaluationRunnable).terminate();
+		synchronized (fEvaluationLock) {
+			if (canTerminateEvaluation()) {
+				fEvaluationInterrupted = true;
+				((ITerminate) fEvaluationRunnable).terminate();
+			}
 		}
 	}
 	
@@ -709,7 +753,9 @@
 	 * @see IJavaThread#canTerminateEvaluation()
 	 */
 	public boolean canTerminateEvaluation() {
-		return fEvaluationRunnable instanceof ITerminate;
+		synchronized (fEvaluationLock) {
+			return fEvaluationRunnable instanceof ITerminate;
+		}
 	}
 
 	/**
@@ -788,7 +834,7 @@
 			preserveStackFrames();
 			int flags= ClassType.INVOKE_SINGLE_THREADED;
 			if (invokeNonvirtual) {
-				// Superclass method invocation must be performed nonvirtual.
+				// Superclass method invocation must be performed non-virtual.
 				flags |= ObjectReference.INVOKE_NONVIRTUAL;
 			}
 			if (receiverClass == null) {
@@ -936,9 +982,6 @@
  	 * @see #newInstance(ClassType, Method, List)
 	 */
 	protected synchronized void invokeComplete(int restoreTimeout) {
-        if (!fIsEvaluatingConditionalBreakpoint) {
-            abortStep();
-        }
 		setInvokingMethod(false);
 		setRunning(false);
 		setRequestTimeout(restoreTimeout);
@@ -949,17 +992,6 @@
 			logError(e);
 		}
 	}
-    
-    /**
-     * Sets whether or not this thread is currently evaluating a conditional
-     * breakpoint expression. This state is maintained as a workaround to problems
-     * that can occur related to the interaction of stepping and expression
-     * evaluation. See bug 81658 for more details.
-     * @param evaluating
-     */
-    public void setEvaluatingConditionalBreakpoint(boolean evaluating) {
-        fIsEvaluatingConditionalBreakpoint= evaluating;
-    }
 	
 	/**
 	 * @see IThread#getName()
@@ -1022,78 +1054,118 @@
 
 	/**
 	 * A breakpoint has suspended execution of this thread.
-	 * Aborts any step currently in process and fires a
-	 * suspend event.
+	 * Aborts any step currently in process and notifies listeners
+	 * of the breakpoint to allow a vote to determine if the thread
+	 * should suspend. 
 	 * 
 	 * @param breakpoint the breakpoint that caused the suspend
+	 * @param suspendVote current vote before listeners are notified (for example, if a step request
+	 *  happens at the same location as a breakpoint, the step may have voted to suspend
+	 *  already - this allows a conditional breakpoint to avoid evaluation) 
 	 * @return whether this thread suspended
 	 */
-	public synchronized boolean handleSuspendForBreakpoint(JavaBreakpoint breakpoint, boolean queueEvent) {
-		addCurrentBreakpoint(breakpoint);
-		setSuspendedQuiet(false);
-		try {
+	public boolean handleSuspendForBreakpoint(JavaBreakpoint breakpoint, boolean suspendVote) {
+		int policy = IJavaBreakpoint.SUSPEND_THREAD;
+		synchronized (this) {
+			if (fClientSuspendRequest) {
+				// a request to suspend has overridden the breakpoint request - suspend and
+				// ignore the breakpoint
+				return true;
+			}
+			fSuspendVoteInProgress = true;
+			addCurrentBreakpoint(breakpoint);
+			try {
+				policy = breakpoint.getSuspendPolicy();
+			} catch (CoreException e) {
+				logError(e);
+				setRunning(true);
+				return false;
+			}
+			
 			// update state to suspended but don't actually
 			// suspend unless a registered listener agrees
-			if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
+			if (policy == IJavaBreakpoint.SUSPEND_VM) {
 				((JDIDebugTarget)getDebugTarget()).prepareToSuspendByBreakpoint(breakpoint);
 			} else {
 				setRunning(false);
 			}
-			
-			// poll listeners
-			boolean suspend = JDIDebugPlugin.getDefault().fireBreakpointHit(this, breakpoint);
-			
-			// suspend or resume
-			if (suspend) {
-				if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
-					((JDIDebugTarget)getDebugTarget()).suspendedByBreakpoint(breakpoint, queueEvent);
-				}
-				abortStep();
-				if (queueEvent) {
-					queueSuspendEvent(DebugEvent.BREAKPOINT);
-				} else {
-					fireSuspendEvent(DebugEvent.BREAKPOINT);
-				}
-			} else {
-				if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
-					((JDIDebugTarget)getDebugTarget()).cancelSuspendByBreakpoint(breakpoint);
-				} else {
-					setRunning(true);
-					// dispose cached stack frames so we re-retrieve on the next breakpoint
-					preserveStackFrames();
-				}				
-			}
-			return suspend;
-		} catch (CoreException e) {
-			logError(e);
-			setRunning(true);
-			return false;
 		}
-	}
-	
-	public void wonSuspendVote(JavaBreakpoint breakpoint) {
-		setSuspendedQuiet(false);
-		try {
-			setRunning(false);
-			if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
-				((JDIDebugTarget)getDebugTarget()).suspendedByBreakpoint(breakpoint, false);
+		
+		// Evaluate breakpoint condition (if any). If the vote is already in a
+		// suspend state, don't bother evaluating the condition.
+		if (!suspendVote) {
+			if (breakpoint instanceof JavaLineBreakpoint) {
+				JavaLineBreakpoint lbp = (JavaLineBreakpoint) breakpoint;
+				if (lbp.hasCondition()) {
+					ConditionalBreakpointHandler handler = new ConditionalBreakpointHandler();
+					int vote = handler.breakpointHit(this, breakpoint);
+					if (vote == IJavaBreakpointListener.DONT_SUSPEND) {
+						// condition is false, breakpoint is not hit
+						synchronized (this) {
+							fSuspendVoteInProgress = false;
+							return false;
+						}
+					}
+				}
 			}
-		} catch (CoreException e) {
-			logError(e);
-		}		
+		}
+			
+		// poll listeners without holding lock on thread
+		boolean suspend = true;
+		try {
+			suspend = JDIDebugPlugin.getDefault().fireBreakpointHit(this, breakpoint);
+		} finally {
+			synchronized (this) {
+				fSuspendVoteInProgress = false;
+				if (fClientSuspendRequest) {
+					// if a client has requested a suspend, then override the vote to suspend
+					suspend = true;
+				}
+			}
+		}
+		return suspend;
 	}
-	
+		
 	/**
-	 * Updates the state of this thread to suspend for
-	 * the given breakpoint  but does not fire notification
-	 * of the suspend. Do no abort the current step as the program
-	 * may be resumed quietly and the step may still finish.
+	 * Called after an event set with a breakpoint is done being processed.
+	 * Updates thread state based on the result of handling the event set.
+	 * Aborts any step in progress and fires a suspend event is suspending.
+	 * 
+	 * @param breakpoint the breakpoint that was hit
+	 * @param suspend whether to suspend 
+	 * @param queue whether to queue events or fire immediately
 	 */
-	public synchronized boolean handleSuspendForBreakpointQuiet(JavaBreakpoint breakpoint) {
-		addCurrentBreakpoint(breakpoint);
-		setSuspendedQuiet(true);
-		setRunning(false);
-		return true;
+	public void completeBreakpointHandling(JavaBreakpoint breakpoint, boolean suspend, boolean queue) {
+		synchronized (this) {
+			try {	
+				int policy = breakpoint.getSuspendPolicy();
+				// suspend or resume
+				if (suspend) {
+					if (policy == IJavaBreakpoint.SUSPEND_VM) {
+						((JDIDebugTarget)getDebugTarget()).suspendedByBreakpoint(breakpoint, false);
+					}
+					abortStep();
+					if (queue) {
+						queueSuspendEvent(DebugEvent.BREAKPOINT);
+					} else {
+						fireSuspendEvent(DebugEvent.BREAKPOINT);
+					}
+				} else {
+					if (policy == IJavaBreakpoint.SUSPEND_VM) {
+						((JDIDebugTarget)getDebugTarget()).cancelSuspendByBreakpoint(breakpoint);
+					} else {
+						setRunning(true);
+						// dispose cached stack frames so we re-retrieve on the next breakpoint
+						preserveStackFrames();
+					}				
+				}
+			} catch (CoreException e) {
+				logError(e);
+				setRunning(true);
+			}			
+		}
+
+		
 	}
 
 	/**
@@ -1111,13 +1183,6 @@
 	}
 
 	/**
-	 * @see ISuspendResume#isSuspended()
-	 */
-	public boolean isSuspendedQuiet() {
-		return fSuspendedQuiet;
-	}
-
-	/**
 	 * @see IJavaThread#isSystemThread()
 	 */
 	public boolean isSystemThread() {
@@ -1201,6 +1266,7 @@
 		if (getDebugTarget().isSuspended()) {
 			getDebugTarget().resume();
 		} else {
+			fClientSuspendRequest = false;
 			resumeThread(true);
 		}
 	}
@@ -1208,18 +1274,6 @@
 	/**
 	 * @see ISuspendResume#resume()
 	 * 
-	 * Updates the state of this thread to resumed,
-	 * but does not fire notification of the resumption.
-	 */
-	public synchronized void resumeQuiet() throws DebugException {
-		if (isSuspendedQuiet()) {
-			resumeThread(false);
-		}
-	}
-	
-	/**
-	 * @see ISuspendResume#resume()
-	 * 
 	 * Updates the state of this thread, but only fires
 	 * notification to listeners if <code>fireNotification</code>
 	 * is <code>true</code>.
@@ -1230,7 +1284,6 @@
 		}
 		try {
 			setRunning(true);
-			setSuspendedQuiet(false);
 			if (fireNotification) {
 				fireResumeEvent(DebugEvent.CLIENT_REQUEST);
 			}
@@ -1258,10 +1311,6 @@
 			fCurrentBreakpoints.clear();
 		} 
 	}
-	
-	protected void setSuspendedQuiet(boolean suspendedQuiet) {
-		fSuspendedQuiet= suspendedQuiet;
-	}
 
 	/**
 	 * Preserves stack frames to be used on the next suspend event.
@@ -1418,20 +1467,75 @@
 	/**
 	 * @see ISuspendResume#suspend()
 	 */
-	public synchronized void suspend() throws DebugException {
-		try {
-			// Abort any pending step request
-			abortStep();
-			setSuspendedQuiet(false);
-			fEvaluationInterrupted = isPerformingEvaluation();
-			suspendUnderlyingThread();
-		} catch (RuntimeException e) {
-			setRunning(true);
-			targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_suspending, new String[] {e.toString()}), e); 
+	public void suspend() throws DebugException {
+		// prepare for the suspend request
+		prepareForClientSuspend();
+		
+		synchronized (this) {
+			try {
+				// Abort any pending step request
+				abortStep();
+				suspendUnderlyingThread();
+			} catch (RuntimeException e) {
+				fClientSuspendRequest = false;
+				setRunning(true);
+				targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_suspending, new String[] {e.toString()}), e); 
+			}			
 		}
 	}
 	
 	/**
+	 * Prepares to suspend this thread as requested by a client. Terminates any current
+	 * evaluation (to stop after next instruction). Waits for any method invocations
+	 * to complete.
+	 * 
+	 * @throws DebugException if thread does not suspend before timeout
+	 */
+	protected void prepareForClientSuspend() throws DebugException {
+		// note that a suspend request has started
+		synchronized (this) {
+			// this will abort notification to pending breakpoint listeners 
+			fClientSuspendRequest = true;
+		}
+		
+		synchronized (fEvaluationLock) {
+			// terminate active evaluation, if any
+			if (fEvaluationRunnable != null) {
+				if (canTerminateEvaluation()) {
+					fEvaluationInterrupted = true;
+					((ITerminate) fEvaluationRunnable).terminate();
+				}
+				// wait for termination to complete
+				int timeout = JDIDebugModel.getPreferences().getInt(JDIDebugModel.PREF_REQUEST_TIMEOUT);
+				try {
+					fEvaluationLock.wait(timeout);
+				} catch (InterruptedException e) {
+				}
+				if (fEvaluationRunnable != null) {
+					fClientSuspendRequest = false;
+					targetRequestFailed(JDIDebugModelMessages.JDIThread_1, null);
+				}
+			}			
+		}
+		
+		// first wait for any method invocation in progress to complete its method invocation
+		synchronized (fInvocationLock) {
+			if (isInvokingMethod()) {
+				int timeout = JDIDebugModel.getPreferences().getInt(JDIDebugModel.PREF_REQUEST_TIMEOUT);
+				try {
+					fInvocationLock.wait(timeout);
+				} catch (InterruptedException e) {
+				}
+				if (isInvokingMethod()) {
+					// timeout waiting for invocation to complete, abort
+					fClientSuspendRequest = false;
+					targetRequestFailed(JDIDebugModelMessages.JDIThread_1, null);
+				}
+			}
+		}		
+	}
+	
+	/**
 	 * Suspends the underlying thread asynchronously and fires notification when
 	 * the underlying thread is suspended.
 	 */
@@ -1493,7 +1597,6 @@
 	 */	
 	protected synchronized void suspendedByVM() {
 		setRunning(false);
-		setSuspendedQuiet(false);
 	}
 
 	/**
@@ -1501,6 +1604,7 @@
 	 * to a VM resume.
 	 */
 	protected synchronized void resumedByVM() throws DebugException {
+		fClientSuspendRequest = false;
 		setRunning(true);
 		preserveStackFrames();
 		// This method is called *before* the VM is actually resumed.
@@ -1723,12 +1827,12 @@
 	 * @see IJavaThread#isPerformingEvaluation()
 	 */
 	public boolean isPerformingEvaluation() {
-		return fIsPerformingEvaluation;
+		return fEvaluationRunnable != null;
 	}
 	
 	/**
 	 * Returns whether this thread is currently performing
-	 * a method invokation 
+	 * a method invocation 
 	 */
 	public boolean isInvokingMethod() {
 		return fIsInvokingMethod;
@@ -1739,17 +1843,32 @@
 	 * breakpoints.
 	 */
 	public boolean isIgnoringBreakpoints() {
-		return !fHonorBreakpoints;
+		return !fHonorBreakpoints || fSuspendVoteInProgress || hasClientRequestedSuspend();
 	}
 	
 	/**
-	 * Sets whether this thread is currently invoking a method
+	 * Returns whether a client has requested the target/thread to suspend.
+	 * 
+	 * @return whether a client has requested the target/thread to suspend
+	 */
+	public boolean hasClientRequestedSuspend() {
+		return fClientSuspendRequest;
+	}
+	
+	/**
+	 * Sets whether this thread is currently invoking a method. Notifies
+	 * any threads waiting for the method invocation lock
 	 * 
 	 * @param evaluating whether this thread is currently
 	 *  invoking a method
 	 */
 	protected void setInvokingMethod(boolean invoking) {
-		fIsInvokingMethod= invoking;
+		synchronized (fInvocationLock) {
+			fIsInvokingMethod= invoking;
+			if (!invoking) {
+				fInvocationLock.notifyAll();
+			}
+		}
 	}
 	
 	/**
@@ -1843,6 +1962,9 @@
 		 */
 		protected void invokeThread() throws DebugException {
 			try {
+				synchronized (JDIThread.this) {
+					fClientSuspendRequest = false;
+				}
 				fThread.resume();
 			} catch (RuntimeException e) {
 				stepEnd();
@@ -2008,7 +2130,7 @@
 		 * 
 		 * @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
 		 */
-		public boolean handleEvent(Event event, JDIDebugTarget target) {
+		public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote) {
 			try {
 				StepEvent stepEvent = (StepEvent) event;
 				Location currentLocation = stepEvent.location();
@@ -2040,12 +2162,10 @@
 			}
 		}
 		
-		
-		
 		/* (non-Javadoc)
-		 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
+		 * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#eventSetComplete(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, boolean)
 		 */
-		public void wonSuspendVote(Event event, JDIDebugTarget target) {
+		public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend) {
 			// do nothing
 		}
 
@@ -2288,9 +2408,9 @@
 		 * another step request is created and this thread
 		 * is resumed.
 		 * 
-		 * @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
+		 * @see IJDIEventListener#handleEvent(Event, JDIDebugTarget, boolean)
 		 */
-		public boolean handleEvent(Event event, JDIDebugTarget target) {
+		public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote) {
 			try {
 				int numFrames = getUnderlyingFrameCount();
 				// tos should not be null
@@ -2385,10 +2505,10 @@
 		 * top frame. Returns false, as this handler will resume this
 		 * thread with a special invocation (<code>doReturn</code>).
 		 * 
-		 * @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
+		 * @see IJDIEventListener#handleEvent(Event, JDIDebugTarget, boolean)
 		 * @see #invokeThread()
 		 */		
-		public boolean handleEvent(Event event, JDIDebugTarget target) {
+		public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote) {
 			// pop is complete, update number of frames to drop
 			setFramesToDrop(getFramesToDrop() - 1);
 			try {
@@ -2742,8 +2862,16 @@
 	public synchronized void resumedFromClassPrepare() {
 		if (isSuspended()) {
 			setRunning(true);
-			setSuspendedQuiet(false);
 			fireResumeEvent(DebugEvent.CLIENT_REQUEST);
 		}
 	}
+	
+	/**
+	 * Returns whether a suspend vote is currently in progress.
+	 * 
+	 * @return whether a suspend vote is currently in progress
+	 */
+	public synchronized boolean isSuspendVoteInProgress() {
+		return fSuspendVoteInProgress;
+	}
 }
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIVoidValue.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIVoidValue.java
index d22150a..f18368e 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIVoidValue.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIVoidValue.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
  * 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
@@ -23,7 +23,7 @@
 	
 	
 	public JDIVoidValue(JDIDebugTarget target) {
-		super(target, target.getVM().mirrorOfVoid());
+		super(target, target.getVM()!=null?target.getVM().mirrorOfVoid():null);
 	}
 
 	protected List getVariablesList() {
diff --git a/org.eclipse.jdt.debug/plugin.properties b/org.eclipse.jdt.debug/plugin.properties
index c499b92..55233bc 100644
--- a/org.eclipse.jdt.debug/plugin.properties
+++ b/org.eclipse.jdt.debug/plugin.properties
@@ -38,3 +38,5 @@
 JavaMethodBreakpoint.name = Java Method Breakpoint
 JavaMethodEntryBreakpoint.name = Java Method Entry Breakpoint
 JavaStratumLineBreakpoint.name = Java Stratum Line Breakpoint
+
+breakpointListeners.name = Java Breakpoint Listeners
diff --git a/org.eclipse.jdt.debug/plugin.xml b/org.eclipse.jdt.debug/plugin.xml
index 2ebed15..820a171 100644
--- a/org.eclipse.jdt.debug/plugin.xml
+++ b/org.eclipse.jdt.debug/plugin.xml
@@ -6,6 +6,7 @@
 <!-- Not to be extended com.sun.tools.jdi.VirtualMachineManagerImpl or org.eclipse.jdi.internal.VirtualMachineManagerImpl -->
    <extension-point id="jdiclient" name="%virtualMachineManagerImpl"  schema="schema/jdiclient.exsd"/>
    <extension-point id="javaLogicalStructures" name="%javaLogicalStructures" schema="schema/javaLogicalStructures.exsd"/>
+   <extension-point id="breakpointListeners" name="%breakpointListeners.name" schema="schema/breakpointListeners.exsd"/>
 
 <!-- Extensions -->
    <extension
diff --git a/org.eclipse.jdt.debug/schema/breakpointListeners.exsd b/org.eclipse.jdt.debug/schema/breakpointListeners.exsd
new file mode 100644
index 0000000..48a05b2
--- /dev/null
+++ b/org.eclipse.jdt.debug/schema/breakpointListeners.exsd
@@ -0,0 +1,153 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.jdt.debug" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appInfo>
+         <meta.schema plugin="org.eclipse.jdt.debug" id="breakpointListeners" name="Java Breakpoint Listeners"/>
+      </appInfo>
+      <documentation>
+         Allow clients to contribute listeners for Java breakpoint notifications.  For example, listeners are called when a breakpoint is hit and about to suspend execution.  The listener can vote to resume or suspend the debug session. Listeners can be programmatically added to and removed from  specific Java breakpoints (specified by breakpoint listener identifers), or be registered to listen for notifications for all Java breakpoints.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appInfo>
+            <meta.element />
+         </appInfo>
+      </annotation>
+      <complexType>
+         <sequence minOccurs="1" maxOccurs="unbounded">
+            <element ref="breakpointListener"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appInfo>
+                  <meta.attribute translatable="true"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="breakpointListener">
+      <complexType>
+         <attribute name="id" type="string" use="required">
+            <annotation>
+               <documentation>
+                  Unique identifier of the breakpoint listener
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="identifier"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  Delegate for breakpoint notifications - must be an instance of &lt;code&gt;IJavaBreakpointListener&lt;/code&gt;
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="java" basedOn=":org.eclipse.jdt.debug.core.IJavaBreakpointListener"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="filter">
+            <annotation>
+               <documentation>
+                  Controls whether the breakpoint listener is notified for all breakpoints. Currently, the only option allowed is &quot;*&quot; indicating all breakpoints. When unspecified, listeners must be added to breakpoints programmatically via their &lt;code&gt;id&lt;/code&gt;.
+               </documentation>
+            </annotation>
+            <simpleType>
+               <restriction base="string">
+                  <enumeration value="*">
+                  </enumeration>
+               </restriction>
+            </simpleType>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="since"/>
+      </appInfo>
+      <documentation>
+         3.5
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="examples"/>
+      </appInfo>
+      <documentation>
+         The following is an example for defining a breakpoint listener. In this example, the listener is only notified of breakpoint events for the specific breakpoints it is programatically registered for.
+
+&lt;p&gt;
+&lt;pre&gt;
+&lt;extension
+       point=&quot;org.eclipse.jdt.debug.breakpointListeners&quot;&gt;
+   &lt;breakpointActionDelegate
+          class=&quot;com.example.BreakpointActionDelegate&quot;
+          id=&quot;com.example.breakpoint.action&quot;&gt;
+    &lt;/breakpointActionDelegate&gt;
+&lt;/extension&gt;
+&lt;/pre&gt;
+&lt;/p&gt;
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="apiinfo"/>
+      </appInfo>
+      <documentation>
+         &lt;p&gt;
+&lt;li&gt;Value of the attribute &lt;b&gt;class&lt;/b&gt; in a &lt;b&gt;breakpointListener&lt;/b&gt; element must be a fully qualifed name of a Java class that implements &lt;b&gt;org.eclipse.jdt.debug.core.IJavaBreakpointListener&lt;/b&gt;.&lt;/li&gt;
+&lt;li&gt;Listeners are added to and removed from a breakpoint programmatically. See &lt;code&gt;IJavaBreakpoint.addBreakpointListener(String id)&lt;/code&gt; and &lt;code&gt;IJavaBreakpoint.removeBreakpointListener(String id)&lt;/code&gt;&lt;/li&gt;
+&lt;/p&gt;
+&lt;br&gt;
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="implementation"/>
+      </appInfo>
+      <documentation>
+         None
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="copyright"/>
+      </appInfo>
+      <documentation>
+         Copyright (c) 2009 IBM Corporation and others.&lt;br&gt;
+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 
+&lt;a href=&quot;http://www.eclipse.org/legal/epl-v10.html&quot;&gt;http://www.eclipse.org/legal/epl-v10.html&lt;/a&gt;
+      </documentation>
+   </annotation>
+
+</schema>