Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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.
* <p>
* This processor uses a background job to process changes asynchronously, avoiding
* blocking of listener threads and allowing coordination with API builder jobs.
* </p>
*
* @since 1.1
*/
Expand All @@ -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<Runnable> 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));
}

/**
Expand Down Expand Up @@ -217,8 +288,26 @@ void processJavaElementDeltas(IJavaElementDelta[] deltas, IJavaProject project)

@Override
public void resourceChanged(IResourceChangeEvent event) {
IResource resource = event.getResource();
switch (event.getType()) {
// 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
*
* @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;
switch (eventType) {
case IResourceChangeEvent.PRE_BUILD: {
if (ApiPlugin.DEBUG_WORKSPACE_DELTA_PROCESSOR) {
if (resource == null) {
Expand All @@ -228,12 +317,11 @@ public void resourceChanged(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) {
Expand Down Expand Up @@ -264,16 +352,14 @@ public void resourceChanged(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) {
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();
Expand All @@ -289,6 +375,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down