Merge remote-tracking branch 'origin/master' into BETA_JAVA9
diff --git a/org.eclipse.jdt.debug.tests/META-INF/MANIFEST.MF b/org.eclipse.jdt.debug.tests/META-INF/MANIFEST.MF
index b74a8b6..bea7112 100644
--- a/org.eclipse.jdt.debug.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.debug.tests/META-INF/MANIFEST.MF
@@ -13,6 +13,7 @@
  org.eclipse.jdt.debug.testplugin.launching,
  org.eclipse.jdt.debug.tests,
  org.eclipse.jdt.debug.tests.breakpoints,
+ org.eclipse.jdt.debug.tests.connectors,
  org.eclipse.jdt.debug.tests.console,
  org.eclipse.jdt.debug.tests.core,
  org.eclipse.jdt.debug.tests.eval,
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 4b4dd11..294d34d 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -41,6 +41,7 @@
 import org.eclipse.jdt.debug.tests.breakpoints.TriggerPointBreakpointsTests;
 import org.eclipse.jdt.debug.tests.breakpoints.TypeNameBreakpointTests;
 import org.eclipse.jdt.debug.tests.breakpoints.WatchpointTests;
+import org.eclipse.jdt.debug.tests.connectors.MultipleConnectionsTest;
 import org.eclipse.jdt.debug.tests.console.ConsoleTerminateAllActionTests;
 import org.eclipse.jdt.debug.tests.console.IOConsoleTests;
 import org.eclipse.jdt.debug.tests.console.JavaStackTraceConsoleTest;
@@ -304,6 +305,7 @@
 		
 	// JDWP tests
 		addTest(new TestSuite(JDWPTests.class));
+		addTest(new TestSuite(MultipleConnectionsTest.class));
 	// Refresh state tests
 		addTest(new TestSuite(RefreshStateTests.class));
 		
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/connectors/MockLaunch.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/connectors/MockLaunch.java
new file mode 100644
index 0000000..35e96d5
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/connectors/MockLaunch.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc. 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:
+ *     Google, Inc. - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jdt.debug.tests.connectors;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.debug.core.model.ISourceLocator;
+
+public class MockLaunch implements ILaunch {
+	private ConcurrentLinkedDeque<IProcess> processes = new ConcurrentLinkedDeque<>();
+	private ConcurrentLinkedDeque<IDebugTarget> targets = new ConcurrentLinkedDeque<>();
+	private ISourceLocator sourceLocator;
+	private Map<String, String> attributes = new HashMap<>();
+
+	@Override
+	public boolean canTerminate() {
+		for (IProcess p : processes) {
+			if (p.canTerminate()) {
+				return true;
+			}
+		}
+		for (IDebugTarget t : targets) {
+			if (t.canTerminate()) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public boolean isTerminated() {
+		for (IProcess p : processes) {
+			if (!p.isTerminated()) {
+				return false;
+			}
+		}
+		for (IDebugTarget t : targets) {
+			if (!t.isTerminated()) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public void terminate() throws DebugException {
+		for (Iterator<IProcess> iter = processes.iterator(); iter.hasNext(); iter.remove()) {
+			IProcess p = iter.next();
+			if (p.canTerminate()) {
+				p.terminate();
+			}
+		}
+		for (Iterator<IDebugTarget> iter = targets.iterator(); iter.hasNext(); iter.remove()) {
+			IDebugTarget t = iter.next();
+			if (t.canTerminate()) {
+				t.terminate();
+			}
+		}
+	}
+
+	@Override
+	public <T> T getAdapter(Class<T> adapter) {
+		return null;
+	}
+
+	@Override
+	public Object[] getChildren() {
+		return new Object[0];
+	}
+
+	@Override
+	public IDebugTarget getDebugTarget() {
+		return null;
+	}
+
+	@Override
+	public IProcess[] getProcesses() {
+		return processes.toArray(new IProcess[processes.size()]);
+	}
+
+	@Override
+	public IDebugTarget[] getDebugTargets() {
+		return targets.toArray(new IDebugTarget[targets.size()]);
+	}
+
+	@Override
+	public void addDebugTarget(IDebugTarget target) {
+		targets.add(target);
+	}
+
+	@Override
+	public void removeDebugTarget(IDebugTarget target) {
+		targets.remove(target);
+	}
+
+	@Override
+	public void addProcess(IProcess process) {
+		processes.add(process);
+	}
+
+	@Override
+	public void removeProcess(IProcess process) {
+		processes.remove(process);
+	}
+
+	@Override
+	public ISourceLocator getSourceLocator() {
+		return sourceLocator;
+	}
+
+	@Override
+	public void setSourceLocator(ISourceLocator locator) {
+		sourceLocator = locator;
+	}
+
+	@Override
+	public String getLaunchMode() {
+		return ILaunchManager.RUN_MODE;
+	}
+
+	@Override
+	public ILaunchConfiguration getLaunchConfiguration() {
+		return null;
+	}
+
+	@Override
+	public void setAttribute(String key, String value) {
+		attributes.put(key, value);
+	}
+
+	@Override
+	public String getAttribute(String key) {
+		return attributes.get(key);
+	}
+
+	@Override
+	public boolean hasChildren() {
+		return getChildren().length > 0;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/connectors/MultipleConnectionsTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/connectors/MultipleConnectionsTest.java
new file mode 100644
index 0000000..caf70b7
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/connectors/MultipleConnectionsTest.java
@@ -0,0 +1,174 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc. 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:
+ *     Google, Inc. - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.jdt.debug.tests.connectors;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.launching.SocketListenConnector;
+import org.eclipse.jdt.launching.SocketUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.sun.jdi.connect.Connector;
+
+/**
+ * Test the SocketListenerConnector
+ */
+public class MultipleConnectionsTest extends AbstractDebugTest {
+
+	public MultipleConnectionsTest(String name) {
+		super(name);
+	}
+
+	private ILaunch launch = new MockLaunch();
+
+	private SocketListenConnector connector;
+
+	private int port;
+
+	@Override
+	@Before
+	protected void setUp() throws Exception {
+		super.setUp();
+		port = SocketUtil.findFreePort();
+	}
+
+	@Test
+	public void testDefaultSettings() throws CoreException {
+		connector = new SocketListenConnector();
+		Map<String, Connector.Argument> defaults = connector.getDefaultArguments();
+		assertTrue(defaults.containsKey("connectionLimit"));
+		assertEquals(1, ((Connector.IntegerArgument) defaults.get("connectionLimit")).intValue());
+	}
+
+	/**
+	 * Ensure out-of-the-box settings mimics previous behaviour of accepting a
+	 * single connection
+	 * 
+	 * @throws IOException
+	 */
+	@Test
+	public void testDefaultBehaviour() throws CoreException, InterruptedException {
+		connector = new SocketListenConnector();
+		Map<String, String> arguments = new HashMap<>();
+		arguments.put("port", Integer.toString(port));
+		connector.connect(arguments, new NullProgressMonitor(), launch);
+		Thread.sleep(200);
+
+		assertTrue("first connect should succeed", connect());
+		assertFalse("second connect should fail", connect());
+	}
+
+	/**
+	 * Ensure connector accepts a single connection
+	 * 
+	 * @throws InterruptedException
+	 */
+	@Test
+	public void testSingleConnectionBehaviour() throws CoreException, InterruptedException {
+		connector = new SocketListenConnector();
+		Map<String, String> arguments = new HashMap<>();
+		arguments.put("port", Integer.toString(port));
+		arguments.put("connectionLimit", "1");
+		connector.connect(arguments, new NullProgressMonitor(), launch);
+		Thread.sleep(200);
+
+		assertTrue("first connect should succeed", connect());
+		assertFalse("second connect should fail", connect());
+	}
+
+	/**
+	 * Ensure out-of-the-box settings mimics previous behaviour of accepting a
+	 * single connection
+	 * 
+	 * @throws InterruptedException
+	 */
+	@Test
+	public void testTwoConnectionsBehaviour() throws CoreException, InterruptedException {
+		connector = new SocketListenConnector();
+		Map<String, String> arguments = new HashMap<>();
+		arguments.put("port", Integer.toString(port));
+		arguments.put("connectionLimit", "2");
+		connector.connect(arguments, new NullProgressMonitor(), launch);
+		Thread.sleep(200);
+
+		assertTrue("first connect should succeed", connect());
+		assertTrue("second connect should succeed", connect());
+	}
+
+	/**
+	 * Ensure out-of-the-box settings mimics previous behaviour of accepting a
+	 * single connection
+	 * 
+	 * @throws InterruptedException
+	 */
+	@Test
+	public void testUnlimitedConnectionsBehaviour() throws CoreException, InterruptedException {
+		connector = new SocketListenConnector();
+		Map<String, String> arguments = new HashMap<>();
+		arguments.put("port", Integer.toString(port));
+		arguments.put("connectionLimit", "0");
+		connector.connect(arguments, new NullProgressMonitor(), launch);
+		Thread.sleep(200);
+
+		for (int i = 0; i < 10; i++) {
+			assertTrue("connection " + i + " should succeed", connect());
+		}
+	}
+
+	@Override
+	@After
+	protected void tearDown() throws Exception {
+		launch.terminate();
+		super.tearDown();
+	}
+
+	private boolean connect() {
+		boolean result = true;
+		// Two try blocks to distinguish between exceptions from socket close (ignorable)
+		// and from dealing with the remote (errors)
+		try (Socket s = new Socket()) {
+			try {
+				s.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
+				byte[] buffer = new byte[14];
+				s.getInputStream().read(buffer);
+				assertEquals("JDWP-Handshake", new String(buffer));
+				s.getOutputStream().write("JDWP-Handshake".getBytes());
+				s.getOutputStream().flush();
+				// Closing gracelessly like this produces
+				// com.sun.jdi.VMDisconnectedExceptions on the log. Could
+				// respond to JDWP to try to bring down the connections
+				// gracefully, but it's a bit involved.
+			} catch (IOException e) {
+				result = false;
+			}
+		} catch(IOException e) {
+		}
+		try {
+			// sleep to allow the remote side to setup the connection
+			Thread.sleep(1000);
+		} catch (InterruptedException ex) {
+			// ignore
+		}
+		return result;
+	}
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java
index a9b44b0..0e90a66 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java
@@ -49,6 +49,7 @@
 	public static String JavaDebugPreferencePage_27;
 
 	public static String JavaDebugPreferencePage_description;
+	public static String JavaDebugPreferencePage_Enable_hot_code_replace_1;
 	public static String JavaDebugPreferencePage_Alert_me_when_hot_code_replace_fails_1;
 	public static String JavaDebugPreferencePage_Alert_me_when_hot_code_replace_is_not_supported_1;
 	public static String JavaDebugPreferencePage_Alert_me_when_obsolete_methods_remain_1;
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties
index ac30067..4b0bf61 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties
@@ -17,6 +17,7 @@
 ImageDescriptorRegistry_Allocating_image_for_wrong_display_1=Allocating image for wrong display
 
 JavaDebugPreferencePage_description=General settings for Java Debugging.
+JavaDebugPreferencePage_Enable_hot_code_replace_1=Enable hot code replace
 JavaDebugPreferencePage_Alert_me_when_hot_code_replace_fails_1=Show error when hot code replace &fails
 JavaDebugPreferencePage_Alert_me_when_hot_code_replace_is_not_supported_1=Show error when hot code replace is not &supported
 JavaDebugPreferencePage_Alert_me_when_obsolete_methods_remain_1=Show error when &obsolete methods remain after hot code replace
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 c1d21b7..bb02b14 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
@@ -1048,7 +1048,7 @@
 			}
 			if (breakpoint.isTriggerPoint()) {
 				flags |= JDIImageDescriptor.TRIGGER_POINT;
-			} else if (!DebugPlugin.getDefault().getBreakpointManager().canSupendOnBreakpoint()) {
+			} else if (DebugPlugin.getDefault().getBreakpointManager().hasActiveTriggerPoints()) {
 				flags |= JDIImageDescriptor.TRIGGER_SUPPRESSED;
 			}
 			if (breakpoint instanceof IJavaLineBreakpoint) {
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java
index 423452f..2cf30b8 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java
@@ -85,6 +85,7 @@
 	private Combo fWatchpoint;
 	
 	// Hot code replace preference widgets
+	private Button fEnableHCRButton;
 	private Button fAlertHCRButton;
 	private Button fAlertHCRNotSupportedButton;
 	private Button fAlertObsoleteButton;
@@ -138,6 +139,7 @@
 		fWatchpoint.setFont(group.getFont());
 			
 		group = SWTFactory.createGroup(composite, DebugUIMessages.JavaDebugPreferencePage_Hot_Code_Replace_2, 1, 1, GridData.FILL_HORIZONTAL);
+		fEnableHCRButton = SWTFactory.createCheckButton(group, DebugUIMessages.JavaDebugPreferencePage_Enable_hot_code_replace_1, null, true, 1);
 		fAlertHCRButton = SWTFactory.createCheckButton(group, DebugUIMessages.JavaDebugPreferencePage_Alert_me_when_hot_code_replace_fails_1, null, false, 1); 
 		fAlertHCRNotSupportedButton = SWTFactory.createCheckButton(group, DebugUIMessages.JavaDebugPreferencePage_Alert_me_when_hot_code_replace_is_not_supported_1, null, false, 1); 
 		fAlertObsoleteButton = SWTFactory.createCheckButton(group, DebugUIMessages.JavaDebugPreferencePage_Alert_me_when_obsolete_methods_remain_1, null, false, 1); 
@@ -199,6 +201,7 @@
 		store.setValue(IJDIPreferencesConstants.PREF_OPEN_INSPECT_POPUP_ON_EXCEPTION, fOpenInspector.getSelection());
 		IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(JDIDebugPlugin.getUniqueIdentifier());
 		if(prefs != null) {
+			prefs.putBoolean(JDIDebugPlugin.PREF_ENABLE_HCR, fEnableHCRButton.getSelection());
 			prefs.putBoolean(JDIDebugModel.PREF_SUSPEND_FOR_BREAKPOINTS_DURING_EVALUATION, fSuspendDuringEvaluations.getSelection());
 			int selectionIndex = fSuspendVMorThread.getSelectionIndex();
 			int policy = IJavaBreakpoint.SUSPEND_THREAD;
@@ -248,6 +251,7 @@
 		fOpenInspector.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_OPEN_INSPECT_POPUP_ON_EXCEPTION));
 		IEclipsePreferences prefs = DefaultScope.INSTANCE.getNode(JDIDebugPlugin.getUniqueIdentifier());
 		if(prefs != null) {
+			fEnableHCRButton.setSelection(prefs.getBoolean(JDIDebugPlugin.PREF_ENABLE_HCR, true));
 			fSuspendDuringEvaluations.setSelection(prefs.getBoolean(JDIDebugModel.PREF_SUSPEND_FOR_BREAKPOINTS_DURING_EVALUATION, true));
 			int value = prefs.getInt(JDIDebugPlugin.PREF_DEFAULT_BREAKPOINT_SUSPEND_POLICY, IJavaBreakpoint.SUSPEND_THREAD);
 			fSuspendVMorThread.select((value == IJavaBreakpoint.SUSPEND_THREAD) ? 0 : 1);
@@ -281,6 +285,7 @@
 		fOpenInspector.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_OPEN_INSPECT_POPUP_ON_EXCEPTION));
 		IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(JDIDebugPlugin.getUniqueIdentifier());
 		if(prefs != null) {
+			fEnableHCRButton.setSelection(prefs.getBoolean(JDIDebugPlugin.PREF_ENABLE_HCR, true));
 			fSuspendDuringEvaluations.setSelection(prefs.getBoolean(JDIDebugModel.PREF_SUSPEND_FOR_BREAKPOINTS_DURING_EVALUATION, true));
 			int value = prefs.getInt(JDIDebugPlugin.PREF_DEFAULT_BREAKPOINT_SUSPEND_POLICY, IJavaBreakpoint.SUSPEND_THREAD);
 			fSuspendVMorThread.select((value == IJavaBreakpoint.SUSPEND_THREAD ? 0 : 1));
diff --git a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.java b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.java
index aa1b36e..d8307c0 100644
--- a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.java
+++ b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.java
@@ -1,12 +1,13 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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 - Initial API and implementation
+ *     IBM - Initial API and implementation
+ *     Google Inc - add support for accepting multiple connections
  *******************************************************************************/
 package org.eclipse.jdi.internal.connect;
 
@@ -52,6 +53,10 @@
 	public static String SocketListeningConnectorImpl_Connection_argument_is_not_of_the_right_type_6;
 	public static String SocketListeningConnectorImpl_Necessary_connection_argument_is_null_7;
 	public static String SocketListeningConnectorImpl_Connection_argument_is_not_a_number_8;
+	public static String SocketListeningConnectorImpl_Limit;
+
+	public static String SocketListeningConnectorImpl_Limit_incoming_connections;
+
 	public static String SocketListeningConnectorImpl_ListeningConnector_Socket_Port;
 	public static String SocketRawLaunchingConnectorImpl_Raw_command_to_start_the_debugged_application_VM_1;
 	public static String SocketRawLaunchingConnectorImpl_Command_2;
diff --git a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.properties b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.properties
index 01c6a55..a10c8bd 100644
--- a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.properties
+++ b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/ConnectMessages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2008 IBM Corporation and others.
+# Copyright (c) 2000, 2016 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
@@ -7,6 +7,7 @@
 #
 # Contributors:
 #     IBM Corporation - initial API and implementation
+#     Google Inc - add support for accepting multiple connections
 ###############################################################################
 
 PacketReceiveManager_Got_IOException_from_Virtual_Machine_1=Got IOException from Virtual Machine
@@ -46,6 +47,8 @@
 SocketListeningConnectorImpl_Connection_argument_is_not_of_the_right_type_6=Connection argument is not of the right type
 SocketListeningConnectorImpl_Necessary_connection_argument_is_null_7=Necessary connection argument is null
 SocketListeningConnectorImpl_Connection_argument_is_not_a_number_8=Connection argument is not a number
+SocketListeningConnectorImpl_Limit=Connection limit:
+SocketListeningConnectorImpl_Limit_incoming_connections=Limit incoming connections (0 = no limit)
 SocketListeningConnectorImpl_ListeningConnector_Socket_Port=ListeningConnector Socket Port=
 SocketRawLaunchingConnectorImpl_Raw_command_to_start_the_debugged_application_VM_1=Raw command to start the debugged application VM
 SocketRawLaunchingConnectorImpl_Command_2=Command:
diff --git a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketConnection.java b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketConnection.java
index 036be8c..858fcc1 100644
--- a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketConnection.java
+++ b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketConnection.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -7,22 +7,32 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Google Inc - add support for accepting multiple connections
  *******************************************************************************/
 package org.eclipse.jdi.internal.connect;
 
 import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.Socket;
 
 import com.sun.jdi.connect.spi.ClosedConnectionException;
 import com.sun.jdi.connect.spi.Connection;
 
 public class SocketConnection extends Connection {
 
-	private SocketTransportService fTransport;
+	// for attaching connector
+	private Socket fSocket;
 
-	SocketConnection(SocketTransportService transport) {
-		fTransport = transport;
+	private InputStream fInput;
+
+	private OutputStream fOutput;
+
+	SocketConnection(Socket socket, InputStream in, OutputStream out) {
+		fSocket = socket;
+		fInput = in;
+		fOutput = out;
 	}
 
 	/*
@@ -32,11 +42,11 @@
 	 */
 	@Override
 	public synchronized void close() throws IOException {
-		if (fTransport == null)
+		if (fSocket == null)
 			return;
 
-		fTransport.close();
-		fTransport = null;
+		fSocket.close();
+		fSocket = null;
 	}
 
 	/*
@@ -46,7 +56,7 @@
 	 */
 	@Override
 	public synchronized boolean isOpen() {
-		return fTransport != null;
+		return fSocket != null;
 	}
 
 	/*
@@ -61,7 +71,7 @@
 			if (!isOpen()) {
 				throw new ClosedConnectionException();
 			}
-			stream = new DataInputStream(fTransport.getInputStream());
+			stream = new DataInputStream(fInput);
 		}
 		synchronized (stream) {
 			int packetLength = 0;
@@ -121,7 +131,7 @@
 			if (!isOpen()) {
 				throw new ClosedConnectionException();
 			}
-			stream = fTransport.getOutputStream();
+			stream = fOutput;
 		}
 
 		synchronized (stream) {
diff --git a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketListeningConnectorImpl.java b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketListeningConnectorImpl.java
index 92c141c..ce62aa4 100644
--- a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketListeningConnectorImpl.java
+++ b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketListeningConnectorImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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,7 @@
  *     IBM Corporation - initial API and implementation
  *     Ivan Popov - Bug 184211: JDI connectors throw NullPointerException if used separately
  *     			from Eclipse
+ *     Google Inc - add support for accepting multiple connections
  *******************************************************************************/
 package org.eclipse.jdi.internal.connect;
 
@@ -58,6 +59,12 @@
 				"timeout", ConnectMessages.SocketListeningConnectorImpl_Timeout_before_accept_returns_3, ConnectMessages.SocketListeningConnectorImpl_Timeout_4, false, 0, Integer.MAX_VALUE); //$NON-NLS-1$  
 		arguments.put(intArg.name(), intArg);
 
+		// FIXME: connectionLimit is not actually used in this class, but in the higher-level controller, SocketListenConnector.  
+		// But IntegerArgumentImpl is package restricted so we must put it here.
+		intArg = new IntegerArgumentImpl("connectionLimit", ConnectMessages.SocketListeningConnectorImpl_Limit_incoming_connections, ConnectMessages.SocketListeningConnectorImpl_Limit, false, 0, Integer.MAX_VALUE); //$NON-NLS-1$
+		intArg.setValue(1);  // mimics previous behaviour, allowing a single connection
+		arguments.put(intArg.name(), intArg);
+
 		return arguments;
 	}
 
diff --git a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketTransportService.java b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketTransportService.java
index b0c62a8..e8b2c75 100644
--- a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketTransportService.java
+++ b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/connect/SocketTransportService.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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,7 @@
  *     IBM Corporation - initial API and implementation
  *     Ivan Popov - Bug 184211: JDI connectors throw NullPointerException if used separately
  *     			from Eclipse
+ *     Google Inc - add support for accepting multiple connections
  *******************************************************************************/
 package org.eclipse.jdi.internal.connect;
 
@@ -73,13 +74,6 @@
 		}
 	}
 
-	// for attaching connector
-	private Socket fSocket;
-
-	private InputStream fInput;
-
-	private OutputStream fOutput;
-
 	// for listening or accepting connectors
 	private ServerSocket fServerSocket;
 
@@ -99,15 +93,16 @@
 			}
 			fServerSocket.setSoTimeout((int) attachTimeout);
 		}
+		Socket socket;
 		try {
-			fSocket = fServerSocket.accept();
+			socket = fServerSocket.accept();
 		} catch (SocketTimeoutException e) {
 			throw new TransportTimeoutException();
 		}
-		fInput = fSocket.getInputStream();
-		fOutput = fSocket.getOutputStream();
-		performHandshake(fInput, fOutput, handshakeTimeout);
-		return new SocketConnection(this);
+		InputStream input = socket.getInputStream();
+		OutputStream output = socket.getOutputStream();
+		performHandshake(input, output, handshakeTimeout);
+		return new SocketConnection(socket, input, output);
 	}
 
 	/*
@@ -141,14 +136,16 @@
 		}
 
 		final IOException[] ex = new IOException[1];
+		final SocketConnection[] result = new SocketConnection[1];
 		Thread attachThread = new Thread(new Runnable() {
 			@Override
 			public void run() {
 				try {
-					fSocket = new Socket(host, port);
-					fInput = fSocket.getInputStream();
-					fOutput = fSocket.getOutputStream();
-					performHandshake(fInput, fOutput, handshakeTimeout);
+					Socket socket = new Socket(host, port);
+					InputStream input = socket.getInputStream();
+					OutputStream output = socket.getOutputStream();
+					performHandshake(input, output, handshakeTimeout);
+					result[0] = new SocketConnection(socket, input, output);
 				} catch (IOException e) {
 					ex[0] = e;
 				}
@@ -169,7 +166,7 @@
 			throw ex[0];
 		}
 
-		return new SocketConnection(this);
+		return result[0];
 	}
 
 	void performHandshake(final InputStream in, final OutputStream out,
@@ -316,34 +313,4 @@
 		}
 		fServerSocket = null;
 	}
-
-	/**
-	 * Closes the current open socket, the transport service will continue to
-	 * listen for new incoming connections.
-	 */
-	public void close() {
-		if (fSocket != null) {
-			try {
-				fSocket.close();
-			} catch (IOException e) {
-			}
-		}
-		fSocket = null;
-		fInput = null;
-		fOutput = null;
-	}
-
-	/**
-	 * @return current socket input stream or <code>null</code>
-	 */
-	public InputStream getInputStream() {
-		return fInput;
-	}
-
-	/**
-	 * @return curernt socket output stream or <code>null</code>
-	 */
-	public OutputStream getOutputStream() {
-		return fOutput;
-	}
 }
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 1fec036..4988e17 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, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -12,6 +12,7 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.debug.core.model.ITriggerPoint;
 
 /**
  * A breakpoint specific to the Java debug model. A Java breakpoint supports:
@@ -29,7 +30,7 @@
  * @noimplement This interface is not intended to be implemented by clients.
  * @noextend This interface is not intended to be extended by clients.
  */
-public interface IJavaBreakpoint extends IBreakpoint {
+public interface IJavaBreakpoint extends IBreakpoint, ITriggerPoint {
 
 	/**
 	 * Suspend policy constant indicating a breakpoint will suspend the target
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 8ff5d0b..64520fa 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
@@ -55,6 +55,14 @@
 public class JDIDebugPlugin extends Plugin implements IEclipsePreferences.IPreferenceChangeListener {
 
 	/**
+	 * Boolean preference controlling if hot code replace is enabled.
+	 * 
+	 * @since 3.11
+	 */
+	public static final String PREF_ENABLE_HCR = JDIDebugPlugin
+			.getUniqueIdentifier() + ".enable_hcr"; //$NON-NLS-1$
+	
+	/**
 	 * integer preference controlling if we should, by default, suspend the VM
 	 * instead of the thread
 	 * 
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPluginPreferenceInitializer.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPluginPreferenceInitializer.java
index a7062c6..080b144 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPluginPreferenceInitializer.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/JDIDebugPluginPreferenceInitializer.java
@@ -32,6 +32,7 @@
 	@Override
 	public void initializeDefaultPreferences() {
 		IEclipsePreferences node = DefaultScope.INSTANCE.getNode(JDIDebugPlugin.getUniqueIdentifier());
+		node.putBoolean(JDIDebugPlugin.PREF_ENABLE_HCR, true);
 		node.putInt(JDIDebugModel.PREF_REQUEST_TIMEOUT,	JDIDebugModel.DEF_REQUEST_TIMEOUT);
 		node.putBoolean(JDIDebugModel.PREF_HCR_WITH_COMPILATION_ERRORS, true);
 		node.putBoolean(JDIDebugModel.PREF_SUSPEND_FOR_BREAKPOINTS_DURING_EVALUATION, true);
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 585417e..c8cff8f 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
@@ -480,7 +480,7 @@
 	protected void disableTriggerPoint(Event event) {
 		try{
 			if (isTriggerPoint() && isEnabled()) {
-					DebugPlugin.getDefault().getBreakpointManager().enableTriggerpoints(null, false);
+					DebugPlugin.getDefault().getBreakpointManager().enableTriggerPoints(null, false);
 					// make a note that we auto-disabled the trigger point for this breakpoint.
 					// we re enable it at cleanup of JDITarget
 				}
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java
index 0b9e4e2..27facfb 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/hcr/JavaHotCodeReplaceManager.java
@@ -444,6 +444,16 @@
 	 */
 	private void doHotCodeReplace(List<JDIDebugTarget> targets, List<IResource> resources,
 			List<String> qualifiedNames) {
+
+		// Check whether hot code replace is enabled
+		if (!Platform.getPreferencesService().getBoolean(
+				JDIDebugPlugin.getUniqueIdentifier(), 
+				JDIDebugPlugin.PREF_ENABLE_HCR, 
+				true, 
+				null)) {
+			return; // disabled
+		}
+
 		MultiStatus ms = new MultiStatus(
 				JDIDebugPlugin.getUniqueIdentifier(),
 				DebugException.TARGET_REQUEST_FAILED,
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 9d930d4..24288c8 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
@@ -1863,7 +1863,7 @@
 		plugin.getBreakpointManager().removeBreakpointManagerListener(this);
 		plugin.removeDebugEventListener(this);
 		removeAllBreakpoints();
-		DebugPlugin.getDefault().getBreakpointManager().enableTriggerpoints(null, true);
+		DebugPlugin.getDefault().getBreakpointManager().enableTriggerPoints(null, true);
 		fOutOfSynchTypes.clear();
 		if (fEngines != null) {
 			Iterator<IAstEvaluationEngine> engines = fEngines.values().iterator();
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 8797935..a9231e9 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
@@ -1430,7 +1430,7 @@
 
 		try {
 			if (!(breakpoint.isTriggerPoint())) {
-				if (!DebugPlugin.getDefault().getBreakpointManager().canSupendOnBreakpoint()){
+				if (DebugPlugin.getDefault().getBreakpointManager().hasActiveTriggerPoints()){
 					fSuspendVoteInProgress = false;
 					return false;
 				}
@@ -1772,7 +1772,7 @@
 				return;
 			}
 		}
-		StepHandler handler = new StepIntoHandler();
+		StepHandler handler = createStepIntoHandler();
 		handler.step();
 	}
 
@@ -1789,7 +1789,7 @@
 				return;
 			}
 		}
-		StepHandler handler = new StepOverHandler();
+		StepHandler handler = createStepOverHandler();
 		handler.step();
 	}
 
@@ -1806,7 +1806,7 @@
 				return;
 			}
 		}
-		StepHandler handler = new StepReturnHandler();
+		StepHandler handler = createStepReturnHandler();
 		handler.step();
 	}
 
@@ -2135,7 +2135,7 @@
 			// This block is synchronized, such that the step request
 			// begins before a background evaluation can be performed.
 			synchronized (this) {
-				StepHandler handler = new DropToFrameHandler(frame);
+				StepHandler handler = createDropToFrameHandler(frame);
 				handler.step();
 			}
 		}
@@ -2204,7 +2204,7 @@
 				return;
 			}
 		}
-		StepHandler handler = new StepToFrameHandler(frame);
+		StepHandler handler = createStepToFrameHandler(frame);
 		handler.step();
 	}
 
@@ -3045,7 +3045,7 @@
 	/**
 	 * Handler for step into requests.
 	 */
-	class StepIntoHandler extends StepHandler {
+	protected class StepIntoHandler extends StepHandler {
 
 		/*
 		 * (non-Javadoc)
@@ -3074,7 +3074,7 @@
 	/**
 	 * Handler for step return requests.
 	 */
-	class StepReturnHandler extends StepHandler {
+	protected class StepReturnHandler extends StepHandler {
 		/*
 		 * (non-Javadoc)
 		 * 
@@ -3120,7 +3120,7 @@
 	 * stack frame). Step returns are performed until a specified stack frame is
 	 * reached or the thread is suspended (explicitly, or by a breakpoint).
 	 */
-	class StepToFrameHandler extends StepReturnHandler {
+	protected class StepToFrameHandler extends StepReturnHandler {
 
 		/**
 		 * The number of frames that should be left on the stack
@@ -3202,7 +3202,7 @@
 	/**
 	 * Handles dropping to a specified frame.
 	 */
-	class DropToFrameHandler extends StepReturnHandler {
+	protected class DropToFrameHandler extends StepReturnHandler {
 
 		/**
 		 * The number of frames to drop off the stack.
@@ -3714,5 +3714,25 @@
 		return (IJavaObject) JDIValue
 				.createValue(getJavaDebugTarget(), fThread);
 	}
+	
+	protected StepIntoHandler createStepIntoHandler() {
+        return new StepIntoHandler();
+    }
 
+    protected StepOverHandler createStepOverHandler() {
+        return new StepOverHandler();
+    }
+
+    protected StepReturnHandler createStepReturnHandler() {
+        return new StepReturnHandler();
+    }
+    
+    protected StepToFrameHandler createStepToFrameHandler(IStackFrame stackFrame) throws DebugException {
+        return new StepToFrameHandler(stackFrame);
+    }
+    
+    protected DropToFrameHandler createDropToFrameHandler(IStackFrame stackFrame) throws DebugException {
+        return new DropToFrameHandler(stackFrame);
+    }
+   
 }
\ No newline at end of file
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnector.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnector.java
index 8ce56d7..7ee5de3 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnector.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnector.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2015 IBM Corporation and others.
+ * Copyright (c) 2007, 2016 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
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Google Inc - add support for accepting multiple connections
  *******************************************************************************/
 package org.eclipse.jdt.internal.launching;
 
@@ -100,11 +101,16 @@
         Connector.Argument param= acceptArguments.get("port"); //$NON-NLS-1$
 		param.setValue(portNumberString);
         
+		// retain default behaviour to accept 1 connection only
+		int connectionLimit = 1;
+		if (arguments.containsKey("connectionLimit")) { //$NON-NLS-1$
+			connectionLimit = Integer.valueOf(arguments.get("connectionLimit")); //$NON-NLS-1$
+		}
+
 		try {
 			monitor.subTask(NLS.bind(LaunchingMessages.SocketListenConnector_3, new String[]{portNumberString}));
 			connector.startListening(acceptArguments);
-			SocketListenConnectorProcess process = new SocketListenConnectorProcess(launch,portNumberString);
-			launch.addProcess(process);
+			SocketListenConnectorProcess process = new SocketListenConnectorProcess(launch, portNumberString, connectionLimit);
 			process.waitForConnection(connector, acceptArguments);
 		} catch (IOException e) {
 			abort(LaunchingMessages.SocketListenConnector_4, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED); 
@@ -119,8 +125,10 @@
 	@Override
 	public Map<String, Connector.Argument> getDefaultArguments() throws CoreException {
 		Map<String, Connector.Argument> def = getListeningConnector().defaultArguments();
+
 		Connector.IntegerArgument arg = (Connector.IntegerArgument)def.get("port"); //$NON-NLS-1$
 		arg.setValue(8000);
+
 		return def;
 	}
 
@@ -131,6 +139,7 @@
 	public List<String> getArgumentOrder() {
 		List<String> list = new ArrayList<String>(1);
 		list.add("port"); //$NON-NLS-1$
+		list.add("connectionLimit"); //$NON-NLS-1$
 		return list;
 	}
 
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnectorProcess.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnectorProcess.java
index c7587c5..15f8c7f 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnectorProcess.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/SocketListenConnectorProcess.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2015 IBM Corporation and others.
+ * Copyright (c) 2007, 2016 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
@@ -7,10 +7,13 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Google Inc - add support for accepting multiple connections
  *******************************************************************************/
 package org.eclipse.jdt.internal.launching;
 
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Map;
 
 import org.eclipse.core.runtime.CoreException;
@@ -41,16 +44,14 @@
 import com.sun.jdi.connect.TransportTimeoutException;
 
 /**
- * A process that represents a VM listening connector that is waiting
- * for a VM to remotely connect.  Allows the user to see the status
- * of the connection and terminate it.  If a successful connection 
- * occurs, the debug target is added to the launch and this process
- * is removed.
+ * A process that represents a VM listening connector that is waiting for some VM(s) to remotely connect. Allows the user to see the status of the
+ * connection and terminate it. If a successful connection occurs, the debug target is added to the launch and, if a configured number of connections
+ * have been reached, then this process is removed.
+ * 
  * @since 3.4
  * @see SocketListenConnector
  */
 public class SocketListenConnectorProcess implements IProcess {
-	
 	/**
 	 * Whether this process has been terminated.
 	 */
@@ -64,19 +65,30 @@
 	 */
 	private String fPort;
 	/**
+	 * The number of incoming connections to accept (0 = unlimited). Setting to 1 mimics previous behaviour.
+	 */
+	private int fConnectionLimit;
+	/** The number of connections accepted so far. */
+	private int fAccepted = 0;
+	/**
 	 * The system job that will wait for incoming VM connections.
 	 */
 	private WaitForConnectionJob fWaitForConnectionJob;
 	
+	/** Time when this instance was created (milliseconds) */
+	private long fStartTime;
+
 	/**
 	 * Creates this process.  The label for this process will state
 	 * the port the connector is listening at.
 	 * @param launch the launch this process belongs to
 	 * @param port the port the connector will wait on
+	 * @param connectionLimit the number of incoming connections to accept (0 = unlimited)
 	 */
-	public SocketListenConnectorProcess(ILaunch launch, String port){
+	public SocketListenConnectorProcess(ILaunch launch, String port, int connectionLimit){
 		fLaunch = launch;
 		fPort = port;
+		fConnectionLimit = connectionLimit;
 	}
 	
 	/**
@@ -95,8 +107,21 @@
 		if (isTerminated()){
 			throw new CoreException(getStatus(LaunchingMessages.SocketListenConnectorProcess_0, null, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED));
 		}
-		fWaitForConnectionJob = new WaitForConnectionJob(this,connector,arguments);
+		fStartTime = System.currentTimeMillis();
+		fAccepted = 0;
+		// If the connector does not support multiple connections, accept a single connection
+		try {
+			if (!connector.supportsMultipleConnections()) {
+				fConnectionLimit = 1;
+			}
+		}
+		catch (IOException | IllegalConnectorArgumentsException ex) {
+			fConnectionLimit = 1;
+		}
+		fLaunch.addProcess(this);
+		fWaitForConnectionJob = new WaitForConnectionJob(connector,arguments);
 		fWaitForConnectionJob.setPriority(Job.SHORT);
+		fWaitForConnectionJob.setSystem(true);
 		fWaitForConnectionJob.addJobChangeListener(new JobChangeAdapter(){
 			@Override
 			public void running(IJobChangeEvent event) {
@@ -104,7 +129,9 @@
 			}
 			@Override
 			public void done(IJobChangeEvent event) {
-				if (event.getResult().equals(Status.CANCEL_STATUS)){
+				if (event.getResult().isOK() && continueListening()) {
+					fWaitForConnectionJob.schedule();
+				} else {
 					try{
 						terminate();
 					} catch (DebugException e){}
@@ -115,6 +142,13 @@
 	}
 
 	/**
+	 * Return true if this connector should continue listening for further connections.
+	 */
+	protected boolean continueListening() {
+		return !isTerminated() && (fConnectionLimit <= 0 || fConnectionLimit - fAccepted > 0);
+	}
+
+	/**
 	 * Returns an error status using the passed parameters.
 	 * 
 	 * @param message the status message
@@ -174,6 +208,7 @@
 	public void terminate() throws DebugException {
 		if (!fTerminated){
 			fTerminated = true;
+			fLaunch.removeProcess(this);
 			if (fWaitForConnectionJob != null){
 				fWaitForConnectionJob.cancel();
 				fWaitForConnectionJob.stopListening();
@@ -236,13 +271,24 @@
 	}
 
 	/**
-	 * Job that waits for incoming VM connections.  When a remote
-	 * VM connection is accepted, a debug target is created and 
-	 * the process that created this job is removed.
+	 * Return the time since this connector was started.
+	 */
+	private String getRunningTime() {
+		long total = System.currentTimeMillis() - fStartTime;
+		StringWriter result = new StringWriter();
+		PrintWriter writer = new PrintWriter(result);
+		int minutes = (int) (total / 60 / 1000);
+		int seconds = (int) (total / 1000) % 60;
+		int milliseconds = (int) (total / 1000) % 1000;
+		writer.printf("%02d:%02d.%03d", minutes, seconds, milliseconds).close(); //$NON-NLS-1$
+		return result.toString();
+	}
+
+	/**
+	 * Job that waits for incoming VM connections. When a remote VM connection is accepted, a debug target is created.
 	 */
 	class WaitForConnectionJob extends Job{
 
-		private IProcess fWaitProcess;
 		private ListeningConnector fConnector;
 		private Map<String, Connector.Argument> fArguments;
 		/**
@@ -253,9 +299,8 @@
 		 */
 		private boolean fListeningStopped = false;
 		
-		public WaitForConnectionJob(IProcess waitProcess, ListeningConnector connector, Map<String, Connector.Argument> arguments) {
+		public WaitForConnectionJob(ListeningConnector connector, Map<String, Connector.Argument> arguments) {
 			super(getLabel());
-			fWaitProcess = waitProcess;
 			fConnector = connector;
 			fArguments = arguments;
 		}
@@ -297,7 +342,7 @@
 				String vmLabel = constructVMLabel(vm, portArg.value(), fLaunch.getLaunchConfiguration());
 				IDebugTarget debugTarget= JDIDebugModel.newDebugTarget(fLaunch, vm, vmLabel, null, allowTerminate, true);
 				fLaunch.addDebugTarget(debugTarget);
-				fLaunch.removeProcess(fWaitProcess);
+				fAccepted++;
 				return Status.OK_STATUS;
 			} catch (IOException e) {
 				if (fListeningStopped){
@@ -306,9 +351,6 @@
 				return getStatus(LaunchingMessages.SocketListenConnectorProcess_4, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED);
 			} catch (IllegalConnectorArgumentsException e) {
 				return getStatus(LaunchingMessages.SocketListenConnectorProcess_4, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED); 
-			} finally {
-				// Always try to close the socket
-				stopListening();
 			}
 		}
 		
@@ -363,6 +405,11 @@
 				}
 			}
 			StringBuffer buffer = new StringBuffer(name);
+			if (fConnectionLimit != 1) {
+				// if we're accepting multiple incoming connections,
+				// append the time when each connection was accepted
+				buffer.append('<').append(getRunningTime()).append('>');
+			}
 			buffer.append('['); 
 			buffer.append(port);
 			buffer.append(']'); 
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMInstallType.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMInstallType.java
index 3d91a2b..d475501 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMInstallType.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMInstallType.java
@@ -15,6 +15,7 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExecutableExtension;
@@ -42,7 +43,7 @@
 	 * Constructs a new VM install type.
 	 */
 	protected AbstractVMInstallType() {
-		fVMs= new ArrayList<IVMInstall>(10);
+		fVMs = new ArrayList<>(10);
 	}
 
 	/* (non-Javadoc)
@@ -61,16 +62,18 @@
 	 */
 	@Override
 	public void disposeVMInstall(String id) {
+		IVMInstall removedVM = null;
 		synchronized (this) {
 			for (int i= 0; i < fVMs.size(); i++) {
-				IVMInstall vm= fVMs.get(i);
-				if (vm.getId().equals(id)) {
-					fVMs.remove(i);
-					JavaRuntime.fireVMRemoved(vm);
-					return;
+				if (fVMs.get(i).getId().equals(id)) {
+					removedVM = fVMs.remove(i);
+					break;
 				}
 			}
 		}
+		if (removedVM != null) {
+			JavaRuntime.fireVMRemoved(removedVM);
+		}
 	}
 
 	/* (non-Javadoc)
@@ -155,7 +158,7 @@
 		synchronized (this) {
 			for (int i = 0; i < fVMs.size(); i++) {
 				IVMInstall vm = fVMs.get(i);
-				if (vm.getName().equals(name)) {
+				if (Objects.equals(vm.getName(), name)) {
 					return vm;
 				}
 			}