diff --git a/m2e-core-tests b/m2e-core-tests
index 813942ef2f..9feae7ba91 160000
--- a/m2e-core-tests
+++ b/m2e-core-tests
@@ -1 +1 @@
-Subproject commit 813942ef2fbc42c989f2104d59d213aa70340d12
+Subproject commit 9feae7ba91aba0894ba76b9fca1b0f33f186c3de
diff --git a/org.eclipse.m2e.launching/plugin.xml b/org.eclipse.m2e.launching/plugin.xml
index fec1e03370..e44cc75756 100644
--- a/org.eclipse.m2e.launching/plugin.xml
+++ b/org.eclipse.m2e.launching/plugin.xml
@@ -243,5 +243,14 @@
class="org.eclipse.m2e.internal.launch.MavenConsoleLineTracker"
processType="java"/>
+
+
+
+
diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionLaunchParticipant.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionLaunchParticipant.java
new file mode 100644
index 0000000000..ac3426a8c1
--- /dev/null
+++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionLaunchParticipant.java
@@ -0,0 +1,79 @@
+/********************************************************************************
+ * Copyright (c) 2022, 2024 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ * Christoph Läubrich - refactor into {@link IMavenLaunchParticipant}
+ ********************************************************************************/
+
+package org.eclipse.m2e.internal.launch;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchesListener2;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
+
+import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime;
+import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge;
+
+
+public class MavenBuildConnectionLaunchParticipant implements IMavenLaunchParticipant {
+
+ static {
+ DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() {
+ public void launchesRemoved(ILaunch[] launches) {
+ ensureClosed(launches);
+ }
+
+ private void ensureClosed(ILaunch[] launches) {
+ Arrays.stream(launches).flatMap(l -> MavenBuildConnectionProcess.get(l).stream())
+ .forEach(MavenBuildConnectionProcess::terminate);
+ }
+
+ public void launchesTerminated(ILaunch[] launches) {
+ ensureClosed(launches);
+ }
+
+ public void launchesAdded(ILaunch[] launches) { // ignore
+ }
+
+ public void launchesChanged(ILaunch[] launches) { // ignore
+ }
+ });
+ }
+
+ public String getProgramArguments(ILaunchConfiguration configuration, ILaunch launch, IProgressMonitor monitor) {
+ try {
+ if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) {
+
+ MavenBuildConnectionProcess process = new MavenBuildConnectionProcess(launch);
+ return M2EMavenBuildDataBridge.prepareConnection(launch.getLaunchConfiguration().getName(), process);
+ }
+ } catch(CoreException | IOException ex) { // ignore
+ }
+ return null;
+ }
+
+ public String getVMArguments(ILaunchConfiguration configuration, ILaunch launch, IProgressMonitor monitor) {
+ return null;
+ }
+
+ public List getSourceLookupParticipants(ILaunchConfiguration configuration, ILaunch launch,
+ IProgressMonitor monitor) {
+ return List.of();
+ }
+
+}
diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java
new file mode 100644
index 0000000000..cac21828cf
--- /dev/null
+++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java
@@ -0,0 +1,155 @@
+/********************************************************************************
+ * Copyright (c) 2024 Christoph Läubrich and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ ********************************************************************************/
+
+package org.eclipse.m2e.internal.launch;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.debug.core.model.IStreamsProxy;
+
+import org.eclipse.m2e.core.embedder.ArtifactKey;
+import org.eclipse.m2e.internal.maven.listener.MavenBuildConnection;
+import org.eclipse.m2e.internal.maven.listener.MavenBuildConnectionListener;
+import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData;
+
+
+/**
+ * This is the representation of the MavenBuildConnection we use to communicate with the remote maven process.
+ */
+public class MavenBuildConnectionProcess implements IProcess, MavenBuildConnectionListener {
+
+ Map> projects = new ConcurrentHashMap<>();
+
+ private ILaunch launch;
+
+ private MavenBuildConnection connection;
+
+ private Map attributes = new HashMap<>();
+
+ private AtomicBoolean terminated = new AtomicBoolean();
+
+ private String label;
+
+ /**
+ * @param launch
+ */
+ public MavenBuildConnectionProcess(ILaunch launch) {
+ this.launch = launch;
+ }
+
+ public T getAdapter(Class adapter) {
+ return null;
+ }
+
+ public boolean canTerminate() {
+ return false;
+ }
+
+ public boolean isTerminated() {
+ return connection != null && connection.isCompleted();
+ }
+
+ public void terminate() {
+ if(connection != null && terminated.compareAndSet(false, true)) {
+ connection.close();
+ fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
+ for(CompletableFuture future : projects.values()) {
+ future.cancel(true);
+ }
+ }
+ }
+
+ public String getLabel() {
+ // TODO fetch the maven version from the remove process like mvn -V ...
+ if(label != null) {
+ return "Maven<" + label + ">";
+ }
+ return "Maven";
+ }
+
+ public ILaunch getLaunch() {
+ return launch;
+ }
+
+ public IStreamsProxy getStreamsProxy() {
+ return null;
+ }
+
+ public void setAttribute(String key, String value) {
+ attributes.put(key, value);
+ fireEvent(new DebugEvent(this, DebugEvent.CHANGE));
+ }
+
+ public String getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ public int getExitValue() {
+ return 0;
+ }
+
+ private static void fireEvent(DebugEvent event) {
+ DebugPlugin manager = DebugPlugin.getDefault();
+ if(manager != null) {
+ manager.fireDebugEventSet(new DebugEvent[] {event});
+ }
+ }
+
+ public static Optional get(ILaunch launch) {
+ for(IProcess process : launch.getProcesses()) {
+ if(process instanceof MavenBuildConnectionProcess p) {
+ return Optional.of(p);
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * @param launch2
+ * @param groupId
+ * @param artifactId
+ * @param version
+ * @return
+ */
+ public CompletableFuture getBuildProject(String groupId, String artifactId, String version) {
+ return projects.computeIfAbsent(new ArtifactKey(groupId, artifactId, version, null),
+ x -> new CompletableFuture<>());
+ }
+
+ public void onOpen(String label, MavenBuildConnection connection) {
+ this.label = label;
+ this.connection = connection;
+ getLaunch().addProcess(this);
+ fireEvent(new DebugEvent(this, DebugEvent.CREATE));
+
+ }
+
+ public void onClose() {
+ terminate();
+ }
+
+ public void onData(MavenProjectBuildData buildData) {
+ projects.computeIfAbsent(new ArtifactKey(buildData.groupId, buildData.artifactId, buildData.version, null), x -> new CompletableFuture<>())
+ .complete(buildData);
+ }
+
+}
diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java
deleted file mode 100644
index de43554632..0000000000
--- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2022, 2022 Hannes Wellmann and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * Hannes Wellmann - initial API and implementation
- ********************************************************************************/
-
-package org.eclipse.m2e.internal.launch;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Stream;
-
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.ILaunch;
-import org.eclipse.debug.core.ILaunchesListener2;
-
-import org.eclipse.m2e.core.embedder.ArtifactKey;
-import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime;
-import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments;
-import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge;
-import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection;
-import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData;
-
-
-public class MavenBuildProjectDataConnection {
-
- private static record MavenBuildConnectionData(Map projects,
- MavenBuildConnection connection) {
- }
-
- private static final Map LAUNCH_PROJECT_DATA = new ConcurrentHashMap<>();
-
- static {
- DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() {
- public void launchesRemoved(ILaunch[] launches) {
- closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::remove));
- }
-
- public void launchesTerminated(ILaunch[] launches) {
- closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::get));
- }
-
- private static void closeServers(Stream connectionData) {
- connectionData.filter(Objects::nonNull).forEach(c -> {
- try {
- c.connection().close();
- } catch(IOException ex) { // ignore
- }
- });
- }
-
- public void launchesAdded(ILaunch[] launches) { // ignore
- }
-
- public void launchesChanged(ILaunch[] launches) { // ignore
- }
- });
- }
-
- static void openListenerConnection(ILaunch launch, VMArguments arguments) {
- try {
- if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) {
-
- Map projects = new ConcurrentHashMap<>();
-
- MavenBuildConnection connection = M2EMavenBuildDataBridge.prepareConnection(
- launch.getLaunchConfiguration().getName(),
- d -> projects.put(new ArtifactKey(d.groupId, d.artifactId, d.version, null), d));
-
- if(LAUNCH_PROJECT_DATA.putIfAbsent(launch, new MavenBuildConnectionData(projects, connection)) != null) {
- connection.close();
- throw new IllegalStateException(
- "Maven bridge already created for launch of" + launch.getLaunchConfiguration().getName());
- }
- arguments.append(connection.getMavenVMArguments());
- }
- } catch(CoreException | IOException ex) { // ignore
- }
- }
-
- static MavenProjectBuildData getBuildProject(ILaunch launch, String groupId, String artifactId, String version) {
- MavenBuildConnectionData build = LAUNCH_PROJECT_DATA.get(launch);
- if(build == null) {
- return null;
- }
- ArtifactKey key = new ArtifactKey(groupId, artifactId, version, null);
- while(true) {
- MavenProjectBuildData buildProject = build.projects().get(key);
- if(buildProject != null || build.connection().isReadCompleted()) {
- return buildProject;
- }
- Thread.onSpinWait(); // Await completion of project data read. It has to become available soon, since its GAV was printed on the console
- }
- }
-
-}
diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java
index cca16682eb..db9fcc4116 100644
--- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java
+++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java
@@ -29,6 +29,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -227,10 +229,13 @@ private String getText(IRegion lineRegion) throws BadLocationException {
if(gaMatcher.matches()) {
String groupId = gaMatcher.group(GROUP_ID);
String artifactId = gaMatcher.group(ARTIFACT_ID);
-
- mavenProject = getProject(groupId, artifactId, version);
- if(mavenProject != null) {
- addProjectLink(line3Region, gaMatcher, GROUP_ID, ARTIFACT_ID, removedLine3Locations);
+ try {
+ mavenProject = getProject(groupId, artifactId, version).join(); //TODO can we do this with future notification?
+ if(mavenProject != null) {
+ addProjectLink(line3Region, gaMatcher, GROUP_ID, ARTIFACT_ID, removedLine3Locations);
+ }
+ } catch(CancellationException e) {
+ mavenProject = null;
}
}
}
@@ -265,18 +270,18 @@ private void addProjectLink(IRegion line, Matcher matcher, int startGroup, int e
console.addLink(link, line.getOffset() + start, end - start);
}
- private ProjectReference getProject(String groupId, String artifactId, String version) {
- MavenProjectBuildData buildProject = MavenBuildProjectDataConnection.getBuildProject(launch, groupId, artifactId,
- version);
- if(buildProject == null) {
- return null;
- }
- IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
- URI basedirURI = buildProject.projectBasedir.toUri();
- Optional project = Arrays.stream(wsRoot.findContainersForLocationURI(basedirURI))
- .filter(IProject.class::isInstance).map(IProject.class::cast).findFirst();
- //if project is absent, the project build in Maven is not in the workspace
- return project.isPresent() ? new ProjectReference(project.get(), buildProject) : null;
+ private CompletableFuture getProject(String groupId, String artifactId, String version) {
+ return MavenBuildConnectionProcess.get(launch).map(process -> process.getBuildProject(groupId, artifactId, version))
+ .map(pdf -> {
+ return pdf.thenApply(buildProject -> {
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+ URI basedirURI = buildProject.projectBasedir.toUri();
+ Optional project = Arrays.stream(wsRoot.findContainersForLocationURI(basedirURI))
+ .filter(IProject.class::isInstance).map(IProject.class::cast).findFirst();
+ //if project is absent, the project build in Maven is not in the workspace
+ return project.isPresent() ? new ProjectReference(project.get(), buildProject) : null;
+ });
+ }).orElseGet(() -> CompletableFuture.completedFuture(null));
}
@Override
diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java
index 892a25aa85..19484380e4 100644
--- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java
+++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java
@@ -98,7 +98,6 @@ public void appendVMArguments(VMArguments arguments, ILaunchConfiguration config
for(IMavenLaunchParticipant participant : participants) {
arguments.append(participant.getVMArguments(configuration, launch, monitor));
}
- MavenBuildProjectDataConnection.openListenerConnection(launch, arguments);
}
}
diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java
index 3e9d9acc94..aa0a8e704b 100644
--- a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java
+++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java
@@ -21,8 +21,6 @@
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -90,7 +88,6 @@ public synchronized boolean isActive() {
}
public synchronized void sendMessage(String message) {
- System.out.println("send: " + message);
if (writeChannel == null) {
return;
}
@@ -109,15 +106,14 @@ public synchronized void sendMessage(String message) {
* Prepares the connection to a {@code Maven build JVM} to be launched and is
* intended to be called from the Eclipse IDE JVM.
*
- * @param label the label of the listener thread
- * @param datasetListener the listener, which is notified whenever a new
- * {@link MavenProjectBuildData MavenProjectBuildDataSet}
- * has arived from the Maven VM in the Eclipse-IDE VM.
- * @return the preapre {@link MavenBuildConnection}
- * @throws IOException
+ * @param label the label of the listener thread
+ * @param listener the listener, which is notified whenever a new
+ * {@link MavenProjectBuildData MavenProjectBuildDataSet} has
+ * arived from the Maven VM in the Eclipse-IDE VM.
+ * @return the maven arguments
+ * @throws IOException if init of connection failed
*/
- public static MavenBuildConnection prepareConnection(String label, Consumer datasetListener)
- throws IOException {
+ public static String prepareConnection(String label, MavenBuildConnectionListener listener) throws IOException {
// TODO: use UNIX domain socket once Java-17 is required by Maven
// Path socketFile = Files.createTempFile("m2e.maven.build.listener", ".socket");
@@ -129,7 +125,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer {
try (ServerSocketChannel s = server; SocketChannel readChannel = server.accept()) {
@@ -140,9 +136,8 @@ public static MavenBuildConnection prepareConnection(String label, Consumer= 0;) {
String dataSet = message.substring(0, terminatorIndex);
message.delete(0, terminatorIndex + DATA_SET_SEPARATOR.length());
- System.out.println("got messagE: " + dataSet);
MavenProjectBuildData buildData = MavenProjectBuildData.parseMavenBuildProject(dataSet);
- datasetListener.accept(buildData);
+ listener.onData(buildData);
}
// Explicit cast for compatibility with covariant return type on JDK 9's
// ByteBuffer
@@ -150,7 +145,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer connection reader");
reader.setDaemon(true);
reader.start();
- return connection;
+ listener.onOpen(label, connection);
+ String port = Integer.toString(((InetSocketAddress) server.getLocalAddress()).getPort());
+ return "-D" + SOCKET_FILE_PROPERTY_NAME + "=" + port;
}
-
- public static final class MavenBuildConnection {
- private final ServerSocketChannel server;
- private final AtomicBoolean readCompleted = new AtomicBoolean(false);
-
- MavenBuildConnection(ServerSocketChannel server) {
- this.server = server;
- }
-
- public String getMavenVMArguments() throws IOException {
- String port = Integer.toString(((InetSocketAddress) server.getLocalAddress()).getPort());
- return "-D" + SOCKET_FILE_PROPERTY_NAME + "=" + port;
- }
-
- public boolean isReadCompleted() {
- return readCompleted.get();
- }
-
- public void close() throws IOException {
- // Close the server to ensure the reader-thread does not wait forever for a
- // connection from the Maven-process in case something went wrong during
- // launching or while setting up the connection.
- server.close();
- }
- }
-
}
diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java
index 5236224268..0f284903bf 100644
--- a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java
+++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java
@@ -39,7 +39,6 @@ public void init(Context context) throws Exception {
@Override
public void onEvent(Object event) throws Exception {
- System.out.println("M2eEventSpy.onEvent() " + event);
if (bridge.isActive()) {
if (event instanceof ExecutionEvent) {
ExecutionEvent executionEvent = (ExecutionEvent) event;
diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnection.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnection.java
new file mode 100644
index 0000000000..de5abb5916
--- /dev/null
+++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnection.java
@@ -0,0 +1,46 @@
+/********************************************************************************
+ * Copyright (c) 2022, 2024 Hannes Wellmann and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Hannes Wellmann - initial API and implementation
+ * Christoph Läubrich - factor out into dedicated component
+ ********************************************************************************/
+package org.eclipse.m2e.internal.maven.listener;
+
+import java.io.IOException;
+import java.nio.channels.ServerSocketChannel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public final class MavenBuildConnection {
+ private final ServerSocketChannel server;
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+ private MavenBuildConnectionListener listener;
+
+ MavenBuildConnection(ServerSocketChannel server, MavenBuildConnectionListener listener) {
+ this.server = server;
+ this.listener = listener;
+ }
+
+ public boolean isCompleted() {
+ return closed.get();
+ }
+
+ public void close() {
+ if (closed.compareAndSet(false, true)) {
+ listener.onClose();
+ // Close the server to ensure the reader-thread does not wait forever for a
+ // connection from the Maven-process in case something went wrong during
+ // launching or while setting up the connection.
+ try {
+ server.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnectionListener.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnectionListener.java
new file mode 100644
index 0000000000..5141f03020
--- /dev/null
+++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnectionListener.java
@@ -0,0 +1,23 @@
+/********************************************************************************
+ * Copyright (c) 2024 Christoph Läubrich and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ ********************************************************************************/
+package org.eclipse.m2e.internal.maven.listener;
+
+public interface MavenBuildConnectionListener {
+
+ void onOpen(String label, MavenBuildConnection connection);
+
+ void onClose();
+
+ void onData(MavenProjectBuildData buildData);
+
+}