Bug 536053 - redefine thread name change listener to JDI listener

Moved ThreadNameChangeListener logic to a JDI listener, to avoid
suspending the target JVM whenever a thread in the Debug View should
change its name.

The listener can be disabled by passing an environment variable to
Eclipse:

-Dorg.eclipse.jdt.internal.debug.core.model.ThreadNameChangeListener.disable=true

Change-Id: I9c8e23b2e461de31412b073ef92be09a258f9c8c
Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java
index de4d889..d40f7a7 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java
@@ -28,7 +28,6 @@
 import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.tests.AbstractDebugTest;
 import org.eclipse.jdt.debug.tests.TestUtil;
-import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
 import org.eclipse.jface.preference.IPreferenceStore;
 
@@ -37,6 +36,8 @@
  */
 public class ThreadNameChangeTests extends AbstractDebugTest {
 
+	private static final String DISABLE_THREAD_NAME_CHANGE_LISTENER = "org.eclipse.jdt.internal.debug.core.model.ThreadNameChangeListener.disable";
+
 	/**
 	 * Constructor
 	 * @param name
@@ -59,8 +60,6 @@
 		IJavaLineBreakpoint bp2 = createLineBreakpoint(bpLine2, "", typeName + ".java", typeName);
 		bp1.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
 		bp2.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
-		boolean oldValue = getPrefStore().getBoolean(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES);
-		getPrefStore().setValue(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES, true);
 		AtomicReference<List<DebugEvent>> events = new AtomicReference<>(new ArrayList<DebugEvent>());
 		IDebugEventSetListener listener = new IDebugEventSetListener() {
 			@Override
@@ -84,7 +83,7 @@
 
 			// expect one single "CHANGE" event for second thread
 			List<DebugEvent> changeEvents = getStateChangeEvents(events, second);
-			assertEquals(1, changeEvents.size());
+			assertEquals("unexpected number of events: " + changeEvents, 1, changeEvents.size());
 
 			// expect that thread name is changed to "2"
 			assertEquals("2", second.getName());
@@ -93,7 +92,6 @@
 			terminateAndRemove(thread);
 			removeAllBreakpoints();
 			DebugPlugin.getDefault().removeDebugEventListener(listener);
-			getPrefStore().setValue(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES, oldValue);
 		}
 	}
 
@@ -103,6 +101,8 @@
 	 * @throws Exception
 	 */
 	public void testListenToThreadNameChangeDisabled() throws Exception {
+		System.setProperty(DISABLE_THREAD_NAME_CHANGE_LISTENER, String.valueOf(Boolean.TRUE));
+
 		String typeName = "ThreadNameChange";
 		final int bpLine1 = 36;
 		final int bpLine2 = 40;
@@ -111,8 +111,6 @@
 		IJavaLineBreakpoint bp2 = createLineBreakpoint(bpLine2, "", typeName + ".java", typeName);
 		bp1.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
 		bp2.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
-		boolean oldValue = getPrefStore().getBoolean(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES);
-		getPrefStore().setValue(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES, false);
 		AtomicReference<List<DebugEvent>> events = new AtomicReference<>(new ArrayList<DebugEvent>());
 		IDebugEventSetListener listener = new IDebugEventSetListener() {
 			@Override
@@ -136,7 +134,7 @@
 
 			// expect no "CHANGE" events
 			List<DebugEvent> changeEvents = getStateChangeEvents(events, second);
-			assertEquals(0, changeEvents.size());
+			assertEquals("expected no events, instead got: " + changeEvents, 0, changeEvents.size());
 
 			// expect that thread name is changed to "2"
 			assertEquals("2", second.getName());
@@ -145,7 +143,7 @@
 			terminateAndRemove(thread);
 			removeAllBreakpoints();
 			DebugPlugin.getDefault().removeDebugEventListener(listener);
-			getPrefStore().setValue(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES, oldValue);
+			System.getProperties().remove(DISABLE_THREAD_NAME_CHANGE_LISTENER);
 		}
 	}
 
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java
index 24d0056..6a1db03 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/JavaDebugTargetTests.java
@@ -23,7 +23,6 @@
 import org.eclipse.jdt.debug.core.IJavaThread;
 import org.eclipse.jdt.debug.tests.AbstractDebugTest;
 import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
-import org.eclipse.jdt.internal.debug.ui.JavaDebugOptionsManager;
 
 /**
  * Tests IJavaDebugTarget API
@@ -178,7 +177,6 @@
 
 	private List<IBreakpoint> getUserBreakpoints(JDIDebugTarget target) {
 		List<IBreakpoint> breakpoints = target.getBreakpoints();
-		breakpoints.remove(JavaDebugOptionsManager.getDefault().getThreadNameChangeBreakpoint());
 		return breakpoints;
 	}
 
diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml
index 0290a93..7d4b03a 100644
--- a/org.eclipse.jdt.debug.ui/plugin.xml
+++ b/org.eclipse.jdt.debug.ui/plugin.xml
@@ -3614,10 +3614,6 @@
           class="org.eclipse.jdt.internal.debug.ui.breakpoints.SuspendOnUncaughtExceptionListener"
           id="org.eclipse.jdt.debug.ui.uncaughtExceptionListener">
     </breakpointListener>
-    <breakpointListener
-          class="org.eclipse.jdt.internal.debug.ui.breakpoints.ThreadNameChangeListener"
-          id="org.eclipse.jdt.debug.ui.threadNameChangeListener">
-    </breakpointListener>
  </extension>
  <extension
        point="org.eclipse.debug.ui.detailPaneFactories">
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java
index e95a611..f401392 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java
@@ -33,11 +33,6 @@
 	public static final String PREF_SUSPEND_ON_COMPILATION_ERRORS = IJavaDebugUIConstants.PLUGIN_ID + ".suspend_on_compilation_errors"; //$NON-NLS-1$
 
 	/**
-	 * Boolean preference controlling whether to listen to thread name changes (while debugging).
-	 */
-	public static final String PREF_LISTEN_ON_THREAD_NAME_CHANGES = IJavaDebugUIConstants.PLUGIN_ID + ".javaDebug.ListenOnThreadNameChanges"; //$NON-NLS-1$
-
-	/**
 	 * Boolean preference controlling whether synthetic
 	 * methods are to be filtered when stepping (and step
 	 * filters are enabled).
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java
index 4246f0f..859781c 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java
@@ -29,7 +29,6 @@
 		IPreferenceStore store = JDIDebugUIPlugin.getDefault().getPreferenceStore();
 		store.setDefault(IJDIPreferencesConstants.PREF_SUSPEND_ON_COMPILATION_ERRORS, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_SUSPEND_ON_UNCAUGHT_EXCEPTIONS, true);
-		store.setDefault(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_ALERT_HCR_FAILED, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_ALERT_HCR_NOT_SUPPORTED, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_ALERT_OBSOLETE_METHODS, true);
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java
index d229f28..eea88e1 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugOptionsManager.java
@@ -62,7 +62,6 @@
 import org.eclipse.jdt.internal.debug.ui.actions.JavaBreakpointPropertiesAction;

 import org.eclipse.jdt.internal.debug.ui.breakpoints.SuspendOnCompilationErrorListener;

 import org.eclipse.jdt.internal.debug.ui.breakpoints.SuspendOnUncaughtExceptionListener;

-import org.eclipse.jdt.internal.debug.ui.breakpoints.ThreadNameChangeListener;

 import org.eclipse.jdt.internal.debug.ui.snippeteditor.ScrapbookLauncher;

 import org.eclipse.jface.preference.IPreferenceStore;

 import org.eclipse.jface.util.IPropertyChangeListener;

@@ -105,11 +104,6 @@
 	private IJavaExceptionBreakpoint fSuspendOnErrorBreakpoint = null;

 

 	/**

-	 * Breakpoint used to listen on thread name changes

-	 */

-	private IJavaMethodEntryBreakpoint fThreadNameChangeBreakpoint;

-

-	/**

 	 * A label provider

 	 */

 	private static ILabelProvider fLabelProvider= DebugUITools.newDebugModelPresentation();

@@ -176,18 +170,6 @@
 				status.add(e.getStatus());

 			}

 

-			// thread name change breakpoint

-			try {

-				IJavaMethodEntryBreakpoint bp = JDIDebugModel.createMethodEntryBreakpoint(ResourcesPlugin.getWorkspace().getRoot(), "java.lang.Thread", "setName", //$NON-NLS-1$ //$NON-NLS-2$

-						"(Ljava/lang/String;)V", -1, -1, -1, 0, false, null); //$NON-NLS-1$

-				bp.setPersisted(false);

-				bp.addBreakpointListener(ThreadNameChangeListener.ID_THREAD_CHANGE_NAME_LISTENER);

-				setThreadNameChangeBreakpoint(bp);

-			}

-			catch (CoreException e) {

-				status.add(e.getStatus());

-			}

-

 			if (status.getChildren().length == 0) {

 				return Status.OK_STATUS;

 			}

@@ -346,15 +328,6 @@
 				}

 				notifyTargets(breakpoint, kind);

 			}

-		} else if (property.equals(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES)) {

-			IBreakpoint breakpoint = getThreadNameChangeBreakpoint();

-			if (breakpoint != null) {

-				int kind = REMOVED;

-				if (isListeningOnThreadNameChanges()) {

-					kind = ADDED;

-				}

-				notifyTargets(breakpoint, kind);

-			}

 		} else if (fgDisplayOptions.contains(property)) {

 			variableViewSettingsChanged();

 		} else if (isUseFilterProperty(property)) {

@@ -425,15 +398,6 @@
 	}

 

 	/**

-	 * Returns whether listening on thread name changes is enabled

-	 *

-	 * @return whether listening on thread name changes is enabled

-	 */

-	public boolean isListeningOnThreadNameChanges() {

-		return JDIDebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IJDIPreferencesConstants.PREF_LISTEN_ON_THREAD_NAME_CHANGES);

-	}

-

-	/**

 	 * Sets the breakpoint used to suspend on uncaught exceptions

 	 *

 	 * @param breakpoint exception breakpoint

@@ -443,25 +407,6 @@
 	}

 

 	/**

-	 * Sets the breakpoint used to listen to thread name changes

-	 *

-	 * @param breakpoint

-	 *            method entry breakpoint

-	 */

-	private void setThreadNameChangeBreakpoint(IJavaMethodEntryBreakpoint breakpoint) {

-		fThreadNameChangeBreakpoint = breakpoint;

-	}

-

-	/**

-	 * Returns the breakpoint used to listen to thread name changes

-	 *

-	 * @return method entry breakpoint

-	 */

-	public IJavaMethodEntryBreakpoint getThreadNameChangeBreakpoint() {

-		return fThreadNameChangeBreakpoint;

-	}

-

-	/**

 	 * Returns the breakpoint used to suspend on uncaught exceptions

 	 *

 	 * @return exception breakpoint

@@ -571,11 +516,6 @@
 						notifyTarget(javaTarget, getSuspendOnCompilationErrorBreakpoint(), ADDED);

 					}

 

-					// thread name change

-					if (isListeningOnThreadNameChanges()) {

-						notifyTarget(javaTarget, getThreadNameChangeBreakpoint(), ADDED);

-					}

-

 					// uncaught exception breakpoint

 					if (isSuspendOnUncaughtExceptions()) {

 						ILaunchConfiguration launchConfiguration = javaTarget.getLaunch().getLaunchConfiguration();

diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/ThreadNameChangeListener.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/ThreadNameChangeListener.java
deleted file mode 100644
index 56c5b3a..0000000
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/ThreadNameChangeListener.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Andrey Loskutov.
- * 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:
- *     Andrey Loskutov <loskutov@gmx.de> - initial API and implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.debug.ui.breakpoints;
-
-import org.eclipse.debug.core.DebugEvent;
-import org.eclipse.debug.core.DebugException;
-import org.eclipse.debug.core.DebugPlugin;
-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.ui.JDIDebugUIPlugin;
-import org.eclipse.jdt.internal.debug.ui.JavaDebugOptionsManager;
-
-/**
- * Breakpoint listener extension for the "thread name change" breakpoint.
- *
- * @since 3.9
- */
-public class ThreadNameChangeListener implements IJavaBreakpointListener {
-
-	public static final String ID_THREAD_CHANGE_NAME_LISTENER = JDIDebugUIPlugin.getUniqueIdentifier() + ".threadNameChangeListener"; //$NON-NLS-1$
-
-	@Override
-	public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
-	}
-
-	@Override
-	public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {
-	}
-
-	@Override
-	public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {
-	}
-
-	@Override
-	public int breakpointHit(IJavaThread thread, IJavaBreakpoint breakpoint) {
-		JavaDebugOptionsManager manager = JavaDebugOptionsManager.getDefault();
-		if (!breakpoint.equals(manager.getThreadNameChangeBreakpoint())) {
-			return DONT_CARE;
-		}
-		if (manager.isListeningOnThreadNameChanges()) {
-			// notify debug view to refresh the thread name
-			DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { new DebugEvent(thread, DebugEvent.CHANGE, DebugEvent.STATE) });
-		}
-		return DONT_SUSPEND;
-	}
-
-	@Override
-	public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
-	}
-
-	@Override
-	public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {
-	}
-
-	@Override
-	public int installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) {
-		return DONT_CARE;
-	}
-
-}
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 bd619c8..ab1ea40 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
@@ -33,6 +33,7 @@
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -93,7 +94,10 @@
 import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint;
 
 import com.ibm.icu.text.MessageFormat;
+import com.sun.jdi.ClassType;
 import com.sun.jdi.InternalException;
+import com.sun.jdi.Location;
+import com.sun.jdi.Method;
 import com.sun.jdi.ObjectCollectedException;
 import com.sun.jdi.ReferenceType;
 import com.sun.jdi.ThreadGroupReference;
@@ -103,6 +107,7 @@
 import com.sun.jdi.VirtualMachine;
 import com.sun.jdi.event.Event;
 import com.sun.jdi.event.EventSet;
+import com.sun.jdi.event.LocatableEvent;
 import com.sun.jdi.event.ThreadDeathEvent;
 import com.sun.jdi.event.ThreadStartEvent;
 import com.sun.jdi.event.VMDeathEvent;
@@ -207,6 +212,11 @@
 	private ThreadStartHandler fThreadStartHandler;
 
 	/**
+	 * Handles changes in thread names, detected via a breakpoint in {@link java.lang.Thread#setName(String)}.
+	 */
+	private ThreadNameChangeHandler fThreadNameChangeHandler;
+
+	/**
 	 * Whether this VM is suspended.
 	 */
 	private boolean fSuspended = true;
@@ -597,6 +607,7 @@
 	 */
 	protected void initializeRequests() {
 		setThreadStartHandler(new ThreadStartHandler());
+		setThreadNameChangeHandler(new ThreadNameChangeHandler());
 		new ThreadDeathHandler();
 	}
 
@@ -842,7 +853,7 @@
 
 		try {
 			setDisconnecting(true);
-			disposeThreadHandler();
+			disposeThreadHandlers();
 			VirtualMachine vm = getVM();
 			if (vm != null) {
 				vm.dispose();
@@ -863,11 +874,15 @@
 	/**
 	 * Allows for ThreadStartHandler to do clean up/disposal.
 	 */
-	private void disposeThreadHandler() {
+	private void disposeThreadHandlers() {
 		ThreadStartHandler handler = getThreadStartHandler();
 		if (handler != null) {
 			handler.deleteRequest();
 		}
+		ThreadNameChangeHandler nameChangeHandler = getThreadNameChangeHandler();
+		if (nameChangeHandler != null) {
+			nameChangeHandler.deleteRequest();
+		}
 	}
 
 	/**
@@ -1747,7 +1762,7 @@
 		}
 		try {
 			setTerminating(true);
-			disposeThreadHandler();
+			disposeThreadHandlers();
 			VirtualMachine vm = getVM();
 			if (vm != null) {
 				vm.exit(1);
@@ -2360,6 +2375,93 @@
 
 	}
 
+	/**
+	 * Triggers updates on a thread when {@link java.lang.Thread#setName(String)} is called on that thread, in the target JVM.
+	 */
+	class ThreadNameChangeHandler implements IJDIEventListener {
+
+		/**
+		 * Environment variable that can be passed down to Eclipse, to disable this listener.
+		 */
+		private static final String DISABLE_THREAD_NAME_CHANGE_LISTENER = "org.eclipse.jdt.internal.debug.core.model.ThreadNameChangeListener.disable"; //$NON-NLS-1$
+		private static final String TYPE_NAME = "java.lang.Thread"; //$NON-NLS-1$
+		private static final String METHOD_NAME = "setName"; //$NON-NLS-1$
+		private static final String METHOD_SIGNATURE = "(Ljava/lang/String;)V"; //$NON-NLS-1$
+
+		private EventRequest request;
+
+		ThreadNameChangeHandler() {
+			String disableListenerSystemProperty = System.getProperty(DISABLE_THREAD_NAME_CHANGE_LISTENER);
+			boolean isDisabled = String.valueOf(Boolean.TRUE).equals(disableListenerSystemProperty);
+			if (!isDisabled) {
+				createRequest();
+			}
+		}
+
+		/**
+		 * Creates a breakpoint request at {@link java.lang.Thread#setName(String)} that doesn't suspend the target JVM.
+		 */
+		void createRequest() {
+			EventRequestManager manager = getEventRequestManager();
+			if (manager != null) {
+				try {
+					Location location = setNameMethodLocation();
+					Assert.isNotNull(location, "Unable to locate Thread.setName method in debuggee JVM"); //$NON-NLS-1$
+					request = manager.createBreakpointRequest(location);
+					request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
+					request.enable();
+					addJDIEventListener(this, request);
+				} catch (RuntimeException e) {
+					String errorMessage = "Failed to add thread name change listener to debug target " + JDIDebugTarget.this; //$NON-NLS-1$
+					IStatus errorStatus = new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), errorMessage, e);
+					JDIDebugPlugin.log(errorStatus);
+				}
+			}
+		}
+
+		private Location setNameMethodLocation() {
+			List<ReferenceType> types = jdiClassesByName(TYPE_NAME);
+			for (ReferenceType type : types) {
+				if (type instanceof ClassType) {
+					Method method = ((ClassType) type).concreteMethodByName(METHOD_NAME, METHOD_SIGNATURE);
+					if (method != null && !method.isNative()) {
+						Location location = method.location();
+						if (location != null && location.codeIndex() != -1) {
+							return location;
+						}
+					}
+				}
+			}
+			return null;
+		}
+
+		void deleteRequest() {
+			if (request != null) {
+				removeJDIEventListener(this, request);
+			}
+		}
+
+		@Override
+		public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
+			ThreadReference ref = ((LocatableEvent) event).thread();
+			JDIThread thread = findThread(ref);
+			if (thread == null) {
+				thread = target.findThread(ref);
+			}
+			if (thread != null) {
+				// trigger updates on the thread
+				DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { new DebugEvent(thread, DebugEvent.CHANGE, DebugEvent.STATE) });
+			}
+			// we never suspend the thread
+			return true;
+		}
+
+		@Override
+		public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
+			// nothing to do here, we do work in handleEvent
+		}
+	}
+
 	class CleanUpJob extends Job {
 
 		/**
@@ -2401,6 +2503,14 @@
 		fThreadStartHandler = threadStartHandler;
 	}
 
+	private ThreadNameChangeHandler getThreadNameChangeHandler() {
+		return fThreadNameChangeHandler;
+	}
+
+	private void setThreadNameChangeHandler(ThreadNameChangeHandler threadNameChangeHandler) {
+		fThreadNameChangeHandler = threadNameChangeHandler;
+	}
+
 	/**
 	 * Java debug targets do not support storage retrieval.
 	 *