From bdca68c6bd15195ae31738ce7d349b93fa9bc002 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 04:41:15 +0000
Subject: [PATCH 1/4] Initial plan
From 8b0310d6731f1d1589dba14af33f6bed6f0efd31 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 04:48:29 +0000
Subject: [PATCH 2/4] Implement job-based WorkspaceDeltaProcessor
Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com>
---
.../internal/WorkspaceDeltaProcessor.java | 96 ++++++++++++++++++-
.../tools/internal/provisional/ApiPlugin.java | 1 +
2 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
index 559527c4c82..f20228d2bba 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2010, 2016 IBM Corporation and others.
+ * Copyright (c) 2010, 2025 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -13,6 +13,8 @@
*******************************************************************************/
package org.eclipse.pde.api.tools.internal;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
@@ -22,6 +24,10 @@
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElement;
@@ -30,6 +36,7 @@
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.pde.api.tools.internal.builder.ApiAnalysisBuilder;
import org.eclipse.pde.api.tools.internal.builder.BuildState;
import org.eclipse.pde.api.tools.internal.model.ApiBaseline;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
@@ -39,6 +46,10 @@
/**
* Standard delta processor for us to track element state changes in the workspace
* using {@link IJavaElementDelta}s and {@link IResourceDelta}s.
+ *
+ * This processor uses a background job to process changes asynchronously, avoiding
+ * blocking of listener threads and allowing coordination with API builder jobs.
+ *
*
* @since 1.1
*/
@@ -47,9 +58,69 @@ public class WorkspaceDeltaProcessor implements IElementChangedListener, IResour
ApiBaselineManager bmanager = ApiBaselineManager.getManager();
ApiDescriptionManager dmanager = ApiDescriptionManager.getManager();
+ /**
+ * Queue of work items to be processed by the delta processing job
+ */
+ private final ConcurrentLinkedQueue workQueue = new ConcurrentLinkedQueue<>();
+
+ /**
+ * The background job that processes workspace changes
+ */
+ private final DeltaProcessingJob processingJob = new DeltaProcessingJob();
+
+ /**
+ * Job for processing workspace deltas asynchronously
+ */
+ private class DeltaProcessingJob extends Job {
+
+ public DeltaProcessingJob() {
+ super("API Tools Workspace Delta Processor"); //$NON-NLS-1$
+ setSystem(true);
+ setPriority(Job.BUILD);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ // Wait for API analysis jobs to complete before processing
+ Job.getJobManager().join(ApiAnalysisBuilder.ApiAnalysisJob.class, monitor);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return Status.CANCEL_STATUS;
+ }
+
+ // Process all queued work items
+ Runnable work;
+ while ((work = workQueue.poll()) != null && !monitor.isCanceled()) {
+ try {
+ work.run();
+ } catch (Exception e) {
+ ApiPlugin.log("Error processing workspace delta", e); //$NON-NLS-1$
+ }
+ }
+
+ return Status.OK_STATUS;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return super.belongsTo(family) || WorkspaceDeltaProcessor.class == family;
+ }
+ }
+
+ /**
+ * Schedules work to be processed by the background job
+ */
+ private void scheduleWork(Runnable work) {
+ workQueue.offer(work);
+ processingJob.cancel(); // Cancel any pending job
+ processingJob.schedule(100); // Debounce: delay to batch multiple changes
+ }
+
@Override
public void elementChanged(ElementChangedEvent event) {
- processJavaElementDeltas(event.getDelta().getAffectedChildren(), null);
+ IJavaElementDelta[] affectedChildren = event.getDelta().getAffectedChildren();
+ scheduleWork(() -> processJavaElementDeltas(affectedChildren, null));
}
/**
@@ -217,6 +288,13 @@ void processJavaElementDeltas(IJavaElementDelta[] deltas, IJavaProject project)
@Override
public void resourceChanged(IResourceChangeEvent event) {
+ scheduleWork(() -> processResourceChange(event));
+ }
+
+ /**
+ * Processes resource change events
+ */
+ void processResourceChange(IResourceChangeEvent event) {
IResource resource = event.getResource();
switch (event.getType()) {
case IResourceChangeEvent.PRE_BUILD: {
@@ -289,6 +367,20 @@ public void resourceChanged(IResourceChangeEvent event) {
}
}
+ /**
+ * Shuts down the delta processor, canceling any pending work and waiting
+ * for the processing job to complete.
+ */
+ public void shutdown() {
+ processingJob.cancel();
+ try {
+ processingJob.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ workQueue.clear();
+ }
+
private void cleanAndDisposeWorkspaceBaseline(IResource resource) {
IJavaProject jp = (IJavaProject) JavaCore.create(resource);
dmanager.clean(jp, true, true);
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java
index 4a3ffee9f6b..a03d16500da 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java
@@ -696,6 +696,7 @@ public void stop(BundleContext context) throws Exception {
if (deltaProcessor != null) {
JavaCore.removeElementChangedListener(deltaProcessor);
ResourcesPlugin.getWorkspace().removeResourceChangeListener(deltaProcessor);
+ deltaProcessor.shutdown();
}
} finally {
super.stop(context);
From 453fdeb56c337f5d94056741ec62eb8dc8cbae39 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 04:49:59 +0000
Subject: [PATCH 3/4] Fix event data capture for thread safety
Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com>
---
.../internal/WorkspaceDeltaProcessor.java | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
index f20228d2bba..74901de4530 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
@@ -288,15 +288,21 @@ void processJavaElementDeltas(IJavaElementDelta[] deltas, IJavaProject project)
@Override
public void resourceChanged(IResourceChangeEvent event) {
- scheduleWork(() -> processResourceChange(event));
+ // Capture event data in the listener thread before scheduling
+ final IResource resource = event.getResource();
+ final int eventType = event.getType();
+ final int buildKind = event.getBuildKind();
+ final IResourceDelta delta = event.getDelta();
+
+ scheduleWork(() -> processResourceChange(resource, eventType, buildKind, delta));
}
/**
* Processes resource change events
*/
- void processResourceChange(IResourceChangeEvent event) {
- IResource resource = event.getResource();
- switch (event.getType()) {
+ void processResourceChange(IResource initialResource, int eventType, int buildKind, IResourceDelta delta) {
+ IResource resource = initialResource;
+ switch (eventType) {
case IResourceChangeEvent.PRE_BUILD: {
if (ApiPlugin.DEBUG_WORKSPACE_DELTA_PROCESSOR) {
if (resource == null) {
@@ -306,12 +312,11 @@ void processResourceChange(IResourceChangeEvent event) {
}
}
- if (event.getBuildKind() == IncrementalProjectBuilder.AUTO_BUILD
+ if (buildKind == IncrementalProjectBuilder.AUTO_BUILD
&& !ResourcesPlugin.getWorkspace().isAutoBuilding()) {
return;
}
- IResourceDelta delta = event.getDelta();
if (delta != null) {
IResourceDelta[] children = delta.getAffectedChildren(IResourceDelta.CHANGED);
for (IResourceDelta element : children) {
@@ -342,11 +347,11 @@ void processResourceChange(IResourceChangeEvent event) {
}
case IResourceChangeEvent.PRE_CLOSE:
case IResourceChangeEvent.PRE_DELETE: {
- if (resource.getType() == IResource.PROJECT) {
+ if (resource != null && resource.getType() == IResource.PROJECT) {
IProject project = (IProject) resource;
if (Util.isApiProject(project) || PluginProject.isJavaProject(project)) {
if (ApiPlugin.DEBUG_WORKSPACE_DELTA_PROCESSOR) {
- if (event.getType() == IResourceChangeEvent.PRE_CLOSE) {
+ if (eventType == IResourceChangeEvent.PRE_CLOSE) {
System.out.println("processed PRE_CLOSE delta for project: [" + resource.getName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
if (ApiPlugin.DEBUG_WORKSPACE_DELTA_PROCESSOR) {
From 4ba14ecf3a74e12ccb9684ea0d3576d1bf70a860 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 04:51:45 +0000
Subject: [PATCH 4/4] Address code review feedback - add javadoc and remove
redundant check
Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com>
---
.../pde/api/tools/internal/WorkspaceDeltaProcessor.java | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
index 74901de4530..841330179d5 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/WorkspaceDeltaProcessor.java
@@ -299,6 +299,11 @@ public void resourceChanged(IResourceChangeEvent event) {
/**
* Processes resource change events
+ *
+ * @param initialResource the resource associated with the event (may be null for workspace-level events)
+ * @param eventType the type of event (e.g., PRE_BUILD, PRE_CLOSE, PRE_DELETE)
+ * @param buildKind the kind of build (for PRE_BUILD events)
+ * @param delta the resource delta (may be null)
*/
void processResourceChange(IResource initialResource, int eventType, int buildKind, IResourceDelta delta) {
IResource resource = initialResource;
@@ -354,9 +359,7 @@ void processResourceChange(IResource initialResource, int eventType, int buildKi
if (eventType == IResourceChangeEvent.PRE_CLOSE) {
System.out.println("processed PRE_CLOSE delta for project: [" + resource.getName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
- if (ApiPlugin.DEBUG_WORKSPACE_DELTA_PROCESSOR) {
- System.out.println("processed PRE_DELETE delta for project: [" + resource.getName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
- }
+ System.out.println("processed PRE_DELETE delta for project: [" + resource.getName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
bmanager.disposeWorkspaceBaseline();